I really like the idea of Jujutsu and was really keen to try it out but when I looked at it several months back I found it really hard to get going with it and ended up just ditching it. This article looks to be a explanation to some of the concepts of `jj` and how to use it in a little more depth than some of the other tutorials I have seen out there. Definitely keen to give it another try at some point
On the flip side, I expected it to be a bigger migration than it was. But I was using it effectively as a complete git replacement the very first day.
I still had a few things I didn’t know how to do optimally, but it was close enough to be productive. Within a week I’d closed basically all of the gaps.
It’s been three or so months and I’m never going back. It’s been so transformative I can barely remember all the innumerable frustrations and papercuts I used to put up with daily. Rebase conflicts. Juggling the stash. Ugh.
I say this as someone who considered themselves extremely proficient with git. I mean, I wrote a compatible Ruby implementation of it over a decade and ago.
If it gains momentum, jj has a better chance than anything I’ve seen at finally dethroning git.
"New VCS, but Git is a first-class backend" is an amazingly smart decision. It's the only chance you actually have of supplanting git. It's a new VCS with enough workflow benefit to be worth making a user switch (in contrast to most git alternate command lines) but provides an easy migration path where you don't have to make a "whole repository" decision. It's an individual engineer decision, that still allows using your existing servers (Github/lab).
I have lot of old local Git branches. Most were pushed on the central server. The central server also have some old branches. Some local branches have been rebased locally. Some have been rebased on the server. Some have been merged on the server (with rebase or not).
I need to clean-up both my local clone and the central server from the branches which contain changes that have been fully merged
Can Jujutsu help with that task?
If not, any other solution?
Yes, almost certainly. Jujutsu has an embedded mini-language to specify revsets. It’s not too hard to make a revset for “all branch names that are descendants of trunk (main/master)”.
You can feed this directly into `jj branch delete`, which will remove your local copy of each branch and flag them for removal from the remote during the next `jj git push`.
Jujutsu seems really cool, and I love that I can use it in a Git repository alongside other contributors that don't. It seems like a lot of logging features from Git aren't present, though.
Besides the mentioned lack of blame functionality[1], things like path filtering (`git log -- <PATH>`), file-following with rename detection (`git log --follow -- <FILE>`), pickaxe (`git log -S <CODE>`), and range evolution (`git log -L<START>,<END>:<FILE>`) are missing. These tools have all become pretty indispensable to me in my day-to-day work since I've learned them, and I know I can still use them since jj can use Git as a backend, but I don't think I could adopt a tool that doesn't have them as first-class features.
Do any regular users of Jujutsu have thoughts on this?
Following up late to this, but, broadly speaking: we mostly just haven't added these features because users haven't asked for them and we haven't otherwise gotten around to it. That's basically it. They would all be useful and/or welcome (assuming we were happy with the implementation.)
(We also don't want to necessarily just straight-up copy raw implementations, but improve on them where we can. But most of the asks here are pretty straightforward, I admit.)
I completely missed the `<PATHS>` argument despite being the first thing documented under `jj log`. That's definitely the most critical feature out of my list, thank you for pointing that out!
Also, it's great to hear that you're willing to accept contributions for those features. If/when Jujutsu gains critical mass, I imagine that someone will end up contributing these features.
What? That's a weirdly money-minded question. I'm not gonna do some Google interview-esque estimation question to answer that, but I'll elaborate on how I use those features, because I enjoy discussing them.
I use line range history the most, multiple times per day, since it's a much better alternative to line blame in most situations. You get the full historical context of a line instead of just the last commit.
File blame is really useful when I'm encountering new code, and I want to quickly find out who has the most "responsibility" over it, oftentimes so I can go and ask them about it if I need more clarification.
File history is especially useful when I come back to a file that I originally wrote or was familiar with, some time has passed, and now I want to re-familiarize myself with all the changes since the last time I was familiar (could be years). Rename detection is useful when there are repository-wide restructures, which has happened a couple of time in the main repository I work on. Otherwise, your history will cut off at that refactor, which is really irritating.
I use path history like file history, but to re-familiarize myself with large modules (admittedly less often than file history, but it comes up).
I'm newer to pickaxe, but I've used it 2 or 3 times in the past year to track some chunks of code throughout a refactor. That's how a lot of these little tools that Git has work. You might only use them a few times per year, but when you need them, they're really amazing.
It seems like Jujutsu focuses a lot on the commit authoring and modification flows, and it looks like it offers improvements in those areas, but I don't see much functionality in the history investigation area. They might be considered niche features to some, but I think this functionality becomes more valuable the older and larger a codebase gets.
> Using Jujutsu, “amending a commit” also produces a new commit object, as in Git, but the new commit has the same change ID as the original.
This is confusing to me, though to be fair I'm a "git expert" by trade. If you're amending a commit surely the "change" has changed so the change ID should also change? If the "change" isn't tracking the actual changes then what could it be tracking?
Overall I think this is just more confusing than using git but I think it's cool that people are building alternative clients. That's definitely the way to go if you want adoption.
Making history manipulation easier seems like a bit of a recipe for disaster given my experience training people. That old XKCD about git comes to mind and honestly that's where most people stay, if you bother to learn it then things like Jujitsu are probably harder to use for you. If you aren't interested in learning git to that level then I doubt you want / need something like Jujitsu.
For those curious the "multiple branches" at a time thing they're selling can be done with git, IMO easily, using worktrees: https://git-scm.com/docs/git-worktree
> If you're amending a commit surely the "change" has changed so the change ID should also change? If the "change" isn't tracking the actual changes then what could it be tracking?
The author is using newer terminology around "changes", but I prefer the older "revisions", as being less overloaded. But yes, the revision/change ID remains the same even if the commits underneath changes. `jj obslog` will show you the history of commits underlying a revision. This stability is what we want when rebasing, and git doesn't provide it.
> Making history manipulation easier seems like a bit of a recipe for disaster ...
I used to think this too, but it was really due to git and its CLI. Under jj, history manipulation is easy, consistent, and easily reversible with `jj undo`. Because it's safer and easier, I routinely do way more rebasing, and stacked PRs are much less painful to incorporate feedback on. Basically, it makes git's more advanced operations feel like everyday tools.
(Of course, jj doesn't fix the problem of rewriting history that's already been shared with other people, but even there, its notion of immutable commits tries to stop you from breaking other people's histories.)
> the "multiple branches" at a time thing they're selling can be done with git, IMO easily, using worktrees
Worktrees aren't quite the same thing the author is describing. Worktrees allow you to check out multiple branches at the same time to different directories, but they're still sort of separate.
The author is making the working revision a merge revision where every parent is a branch they want to work on. This allows them to see what the code will do when those branches are merged. They can also add revisions to all branches simultaneously by working on the merge rev, and using `jj squash` to choose which parent branch to push work to on the fly. When done for the day, `jj abandon` the merge commit. AFAICT, it's both lighter and more flexible than worktrees.
- "commit" is overloaded with git, and jj still uses commits for other things under the hood.
- "patch id" is overloaded with patch files, and jj still uses git's snapshots, not patches (unlike darcs/pijul, iiuc)
- "patch revision id" isn't bad, but it's a bit wordy
- "change id" just seems vague, since it's unclear where one change begins and another ends
"revision" at least captures the idea that you are revising the same piece of functionality, but then you might expect each snapshot/commit to be a different revision, and not have the same ID, which also isn't quite right.
patch sounds too specific... like an actual patch file tied to the actual contents of the patch.
change is probably the right word, you want to change something, the exact operations of the change (multiple revisions of different patches) can evolve over time.
Maybe because I have never used an actual patch file, but patch just feels right to me. As an end user, a patch is an intentional delta blob resulting in some difference to the software. Writing software is just organizing those deltas. If I need to cherry pick between branches, pulling a patch from one to another feels more right than “changes” as a collective object.
Think of the change ID as a "symbolic name", as opposed to the commit ID which identifies a particular snapshot.
As you amend a commit, it creates a new commit ID each time. Only the commit ID of the most recent amendment is in your final graph. All the old ones are orphaned for garbage collection.
The change ID never changes as you make amendments (because you're still working on the same change), and this change ID will always be in the graph. So you can refer to the change ID (which doesn't change) instead of the commit ID (which changes with every amendment).
Another way to think of it would be akin to filenames vs inodes in a filesystem (it's not 100% the same, but the concept should help you visualize). If you delete a file and create another one with the same name, its filename will be the same, but its inode number will be different because it's technically a different file. The old inode gets marked deleted so that it can be reaped somehow. If you make a symbolic link to the file, you'll always get the intended one (because a symbolic link refers to a path). If you make a hard link to the file, you'll get an outdated file after something replaces it (because a hard link refers to an inode).
There’s still a git-compatible commit ID which changes along with the contents. There’s also an immutable revision/change identifier that persists even as you continue working on it.
This works extremely well in practice and makes rebase-heavy workflows practical even when collaborating with others.
…can be done with git, IMO easily, using worktrees
Like many things in git, the capability exists, but I would not call worktrees easy. The few times I have tried to play with worktrees resulted in enough friction that it felt safer to use a clone in a separate directory.
I’ve also run into problems with tools that aren’t worktree aware so often that I’ve stopped using it.
I’ve been using jujutsu for about 6 months now, and the only time I’ve reached back for git was when I had to rebase and amend someone else’s branch to get it merged (when they weren’t available to do so themselves of course).
Switching between changes in jujutsu has been a pleasant experience for me thus far, although I’m not as good with it as I was with stacked-git to keep local only changes (things I’m hacking to match my workflow / local setup) out of change sets.
The way it displays diffs is also still something I am getting used to, and have made plenty of mistakes when pulling in changes from trunk. That’s probably more of a case of “old dog new tricks” than jujutsu.
Yeah, after the first month of jj, I abandoned git forever, because it's already so much better. There are some hiccups, though.
I switched over to colocation for all repos, because too many things expect git directories to be where they expect.
I think the revset language is cool and powerful, but if I'm honest, it's tempting me to spend too much time trying to master, when 99% of the time all I need is, "show me the nearby ancestors and descendants within k revisions".
I think the diffs need work. Or I need to get comfy with 3-way diffs. It's unfamiliar, and an obstacle to fixing conflicts. Luckily I get maybe 1/10th the conflicts I used to under git.
> I think the revset language is cool and powerful, but if I'm honest, it's tempting me to spend too much time trying to master, when 99% of the time all I need is, "show me the nearby ancestors and descendants within k revisions".
I just spend enough time to write a new function for what I want to do, and then just know the basics for regular day to day stuff. I feel like that gets me really far.
> I think the diffs need work. Or I need to get comfy with 3-way diffs. It's unfamiliar, and an obstacle to fixing conflicts.
You should get comfy, you won't regret it. I haven't got around to trying jj yet, but I use them in git; frequently see people messing things up or just having a hard time resolving a conflict that they wouldn't if they used (& understood) diff3.
In brief: a regular 2-way diff shows you the current state, and what you wanted to change to right? Well 3-way just adds an extra bit of information (the middle) which shows you from what state you were changing to the bottom.
So say you have:
<<<<<< HEAD
def wazzle(widget):
try:
widget.wazzle()
except Exception:
return False
return True
|||||||
def wazzle(widget):
widget.wazzle()
=======
def wazzle(widget):
from wazzler import wazzle
wazzle(widget)
>>>>>>> (deadbeef Abstract wazzle implementation to own package)
If you didn't have the middle, it might not be at all clear why you were getting the conflict, and what the appropriate fix is. It allows you to see that Ah ok, master (or whatever I'm rebasing on or whatever) has changed to return a bool indicating success or error, that's fine, I was just trying to change the wazzle method to pass it to a library function instead.
Or you might have it that the same change is already in the HEAD part at the top, but there's a conflict because they put the import elsewhere. The middle then allows you to see that you were making essentially the same change, you don't care where the import goes (or like their idea better), you can just remove it and stick with the changes on HEAD.
My point is that it's strictly more information, that can help or make it easier to resolve the conflict. It shouldn't be confusing at all, because the same 2-part thing you're accustomed to is there too.
Others have explained the change IDs already in detail, but I want to try to give a short and sweet explanation: changes in Jujutsu are a mutable, high level abstraction built on gits immutable commits. Change IDs are stable across mutations.
I really tried to like jj but I couldn’t make it work for my workflow:
There are files that are committed to repository that I need to edit (e.g. .envrc files, which cannot be overridden). There is no way I can ignore those in Jujutsu.
In plain git I can do sparse checkout using negative paths and it works. jj doesn’t support it, and using positive path doesn’t work as I never know if new files are there.
Every push was a dance around removing my changes. I’m checking if there’s progress from time to time but there’s no so far.
But just yesterday I decided to check git branchless as I’m exploring stacked PR workflow and I can say it’s intriguing and even if more rough around the edges than jj I haven’t yet found any showstoppers.
I would say that having files in the repo that you have to change but are not allowed to commit is more an issue with the project setup than with jj. The way this is usually done (at least where I work) is to have e.g. an `.env.example` file in the repo containing default values which the developer copies to `.env` after cloning the project (usually done by a setup script) - `.env` can then be changed, but is in `.gitignore`, so you don't accidentally commit it.
There are always edge cases. VSCode still does not let you have a personal configuration file for a project[0] (open since 2016). There are workarounds, but many of them are less good than just manually ignoring the modified file.
I think OOP meant to say that the `.envrc` file _is_ committed, but they want to do local changes _without_ the possibility of them getting accidentally committed by mistake.
the simple workaround would be to have .envrc optionally load another gitignored file if it exists, which sounds much safer than accidentally committing local changes, even with plain git.
If that's not possible, maybe OP can rename it to .envrc.example, and commit that. Then put in the instructions to rename .envrc.example to .envrc on checkout
Unfortunately neither of that worked, as those are multiple monorepos with different code owners. JJ is nice, but not worth that much of a work around it..
Are Jujutsu users all using it from the command line ? Is there anything magit-like yet ? Or do you use magit with it ? Have you run into extra complexity and messes because of having two VCSes interacting in one working copy ?
> Are Jujutsu users all using it from the command line ?
Yes. Even with no UIs or editor support, it's already a better experience than git for me.
> Have you run into extra complexity and messes because of having two VCSes interacting in one working copy ?
For tools that expect git, you can assist them by using a colocated repository, so they can still see git. It works well enough, though most of the time, they will think you're on a detached HEAD.
That being said, I avoid git's mutating operations. I've heard that can cause trouble.
I'm an extremely light user (just on a few small projects), but
> Have you run into extra complexity and messes because of having two VCSes interacting in one working copy ?
Well, making git commits in a colocated jujutsu repository was a very confusing experience, because at some point I realized I wasn't on a git branch like I thought I was (after a few commits!). jj somehow magically did the right thing though and after switching back to it I had the commits I tried to make in jj and they worked just fine.
This is essentially GitButler's 'integration branch' idea, but via CLI. I tried it briefly, found it frustrating to use a GUI and not helped by (it's early days) bugs.
It occurred to me I could just do the same 'manually' or with some git aliases to help, but I haven't, and I'm glad I've seen this (and the reminder to try out jj in general) because it's certainly far nearer than it would be in pure git.
All these things just remind me how depressing it is that we all use this deeply flawed tool, git, and no one can get enough critical mass for a substantially better version.
the world if filled with "good enough" tools and I don't foresee that ever changing tbh. You'd have to offer something really substantially better than the status quo to gain critical mass. That being said, I think the only thing about git that doesn't make reasonable sense are the damn command line flags and subcommand names. The actual semantics of git are pretty much exactly what you want for source control.
> I have been using the Git backend for the last seven months, full time, on every one of my personal repositories and all the open source projects I have contributed to. With the sole exception of someone watching me while we pair, no one has noticed, because the Git integration is that solid and robust. This interop means that adoption can be very low friction. Any individual can simply run jj git init --git-repo . in a given Git repository, and start doing their work with Jujutsu instead of Git, and all that work gets translated directly into operations on the Git repository.
Well that sounds... pretty good actually! Makes me want to try it too...
Three or four for me. Or, however long since there was a jj introduction posted here that got some traction. The only person on my team who’s even aware is the one other person I converted. The interop is that smooth.
I feel like you and I must have the same taste in technologies. I got into Rails (and Ruby itself) around 2007 then fell in love with Rust around 1.0. Now here I find out you’re a jj convert too!
... I want it to be buck2, but I'm still not quite sure how exactly stuff is shaking out. If the open source prelude was better, I'd use it over Cargo.
Nah, git has lots of bad design choices that are deeper than UI. The index doesn't need to exist. There is a lot of state that is not recorded anywhere - history lost forever. The rebase vs merge disaster. Etc etc.
> git has lots of bad design choices that are deeper than UI. The index doesn't need to exist.
The index is a useful tool. You can opt out with `git commit -a`
> There is a lot of state that is not recorded anywhere
Examples?
> The rebase vs merge disaster
??? How is this a disaster? They serve different purposes and have entirely different semantics. You can use rebase to force fast-forward merges (which do not create merge commits), but I fail to see how this is a disaster.
You cannot "opt out" of the index. It's always sitting there.
$ ls -l .git/index
When it gets corrupt, though, you can blow it away. It gets rebuilt.
The reason it can be casually rebuilt is that there is a copy of it in the HEAD commit.
The git index is an intestinal appendix. It has no reason to exist.
There is no need to "stage" changes and then move them to a commit. What is called staging should just create a new commit.
The index creates duplicity. Many commands operate on both the work tree and index, or separately on either one.
Your working tree can differ from the index, which differs from the HEAD commit, so then you have "git diff" (tree to index), "git diff --cached" (index to HEAD) and "git diff HEAD" (tree to HEAD).
What's the point of staging a commit, when a commit can be amended? You need staging for something that cannot be easily fixed once it is deployed.
To be clear, jj retains the full power of the git index, without having one. To use git terms, the index is just a normal commit like any other. The index is one of my favorite features of git. I don’t miss it in jj.
> Examples?
For one… the index! It’s not part of your history. The working directory can contain dirty state that’s not tracked anywhere. Those are the big two off the top of my head.
> how is this a disaster?
I’m on your side with this one, and jj supports both just fine.
Not a disaster, but merge was not strictly needed. We could've lived with rebase only.
But in git (unlike in jj) rebase is deeply flawed. If you ever needed to resolve the same conflict through several commits you know it. That flawed git implementation is why people kept using merge yo solve the problem on a single final commit.
My main issues with git are all about version control of branches.
Which branches were commits originally created on.
What commit was branch X on 2 weeks in the past?
(My personal biggest issue) -- there is no way to delete a branch in a version-controlled way (that is, let me un-delete it later). 'reflogs' don't count, I want to be able to version-control my branch deletions.
And if people (I'm not accusing you, person I'm replying to, but I get this a lot) say "git isn't for that", well then it's really annoying that git has become "the version control system for everything".
This will sound flippant but it's an honest question: who cares?
I use git all day, every day. Sometimes CLI, sometimes a dedicated UI (e.g. GH desktop but have also used Tower and other dedicated tools), sometimes a UI in something else a la VSCode. I write code every day.
Why should I care at all about rebase vs. merge, whether the internals of git are optimal, whether index needs to exist at all or what it even is?
I think this is sort of what the other comment is getting at about a replacement needing to be much better and that git is good enough. I can count on one hand the number times I've ever needed to glean information out of old commit messages and when something got merged into something else.
Arguing about git workflows and whether you should rebase, merge, squash merge, trunk deployment, gitflow, etc. all seems like ultimate bikeshedding to me. If you're shipping code and making money your git workflow is irrelevant. If your project is so convoluted and broken and you're gaining actual useful information from a 2-year old commit message that you couldn't get just by looking at the code it feels like something else is fundamentally broken that better git internals are not going to address.
I mean, you can use this logic to favor the status quo in any given circumstance. Some people are of course happy with that, but some folks want to try and improve. Not everyone has to care, but not everyone has to not care, either.
I swapped to jj because it’s both easier to use and more powerful than git. To me, anyway. And I was a “you can take git from my cold dead fingers” type person. A “the git cli is fine” type of person.
I would care if a new VCS would make my life easier. This quote from the linked article:
> very few working developers have a good mental model for Git. Instead, they have a handful of commands they have learned over the years: enough to get by, and little more.
...pretty much describes me TBH. I know enough of git to get by day to day, but if I need to do more than that, I have to rely on StackOverflow et al. Which is always frustrating and feels like a waste of time.
I would try a new VCS if it made my life easier too, but I think my point is just that the quote you posted is not only accurate for most developers, but that there's nothing wrong with that. Having a perfect mental model of how git works is going to be a waste of time for anyone who doesn't want to actually work on git itself.
There was a time when a VCS could just be a slightly better VCS and it could probably gain traction.
But now, for both better and worse, git is no longer just an app or a tool, it’s just a fundamental library and protocol that IDE:s, Build systems and project/work management systems build on. It has become almost too late to change.
The semantics of git are “ok”, but mostly it’s just a big leap ahead of cvs and Subversion. As always in any git thread we’ll see people (including me) expressing hope that Pijul will one day be successful enough to break the git monopoly.
> how depressing it is that we all use this deeply flawed tool, git
Maybe a change in perspective can help you be less depressed. I don't find git "deeply flawed"; on the contrary, it's an extremely useful tool with an easy to grasp data model. Some parts of it are quirky, sure, but I find it very easy to use productively in code bases small and large, private or shared.
It's such a huge improvement over what was used before, it can be a little weird for me to hear people complain so hard about git. But hey, some fresher perspective than mine surely exist.
Anyway, instead of thinking of git as deeply flawed, try to see it as a battle tested tool without which your job would suck in ways you can't even imagine.
You're standing on the shoulders of a giant, easily wielding its power, and it sounds like the wart you found on its chin is a huge catastrophe somehow. Find a way to appreciate the giant until a worthy successor inevitably emerges, and maybe things will seem a little less depressing. :)
>Anyway, instead of thinking of git as deeply flawed, try to see it as a battle tested tool without which your job would suck in ways you can't even imagine.
you can start using sapling today. its github compatible and production ready. as long as the new tools support github or whatever forge/hub you are using, there is no need for critical mass for adoption. This is not possisble for radical approaches like pijul, but that approach is not fully proven yet.
Isn't sapling only compatible with git servers and not local repos? That is a huge impediment with so many tools expecting git. That is what i find impressive about jj is it is compatible with local repos.
that is true, i have a separate clone of my repos with sapling and git and use 98% of the time the sapling one with sapling and most importantly interactive smartlog (which is 1000 times better than most git tooling so would be reason alone to do this.) for the few times i need my git tools i sync the git clone via the git remote and then use the git tools on that repo.
Not my experience at all. Most devs I have met in real life love git. It is certainly not perfect but it is good and way better than what came before. I would be happy if someone wrote a replacement which improved substantially upon git but if that never happens that would be fine too.
Having used both, jj imposes dramatically less cognitive overhead.
I was (am?) a git expert and power user. In a month I began trusting jj to a level I never did after using git shortly after its first official releases. I “blindly” undertake repo changes that I’d double or triple check before doing in git.
It’s better to a degree I never would have believed before I gave it a shot.
I would say it's about the same. jj adds some concepts, like change/revisions and revsets, but it also has lower cognitive load than git on things like branches, the index, and the CLI. The ease of undoing anything alone makes it way easier to learn jj, since you can be fearless.
It's not quite clear to me how much simpler DVCS concepts can be.
GG, a GUI for Jujutsu - https://news.ycombinator.com/item?id=39713896 - March 2024 (2 comments)
jj init – getting serious about replacing Git with Jujutsu - https://news.ycombinator.com/item?id=39232456 - Feb 2024 (110 comments)
Jujutsu: A Git-compatible DVCS that is both simple and powerful - https://news.ycombinator.com/item?id=36952796 - Aug 2023 (261 comments)
Jujutsu: A Git-compatible DVCS that is both simple and powerful - https://news.ycombinator.com/item?id=36371138 - June 2023 (1 comment)
Jujutsu – A Git-compatible DVCS that is both simple and powerful - https://news.ycombinator.com/item?id=30398662 - Feb 2022 (228 comments)