> The idea, particularly as realized in the GitHub pull request workflow, is that the real “unit of change” is a pull request, and the individual commits making up a PR are essentially irrelevant.
I loathe GitHub PRs because of this. Working at $dayjob the unit of change is the commit, and every commit is reviewed and signed off by at least 1 peer.
And you know what? I love it. Yes, there's some overhead. But I can understand each commit in its entirety. I and my coworkers have caught numerous issues in code with these single-purpose commits of digestible size.
Compare this to GitHub PRs, which tend to be beastly things that are poorly structured (not to mention GitHub's UI only adding to the review problems...) and multipurpose. Reviewing these big PRs with care is just so much harder. People don't care about the commit message, so looking at the git log it's just a mess that's hard to navigate.
My ideal workflow is commits are as small as possible and PRs "tell a story", meaning that they provide the context for a commit.
I will split up a PR into
- Individual steps a a refactor, especially making any moves their own commits
- I add tests *before* the feature (passing, showing the old behavior)
- The actual fix or feature commit is tiny, with the diff of the tests just demontstrating how behavior changed
This makes it really fast to review a PR because you can see the motivation while only looking at a small change. No jumping around trying to figure out how the pieces fit together.
The main reason I might split up a PR like that is if one piece is likely to generate a discussion before its merged. I then make that a separate PR and as small as possible so we can have a focused discussion.
>Working at $dayjob the unit of change is the commit, and every commit is reviewed and signed off by at least 1 peer.
Respectfully, that's the dumbest thing I've ever heard.
Work on your feature until it's done, on a branch. And then when you're ready, have the branch reviewed. Then squash the branch when merging it, so it becomes 1 commit.
Commit and push often, on branches, and squash when merging. Don't review commits, review the PR.
I've had people at various jobs accidentally delete a directory, effectively losing all their progress, sometimes weeks worth of work. I've experienced laptops being stolen.
If I used your system, over the years me and various colleagues would have lost work irretrievably a few times now, potentially sinking a startup due to not hitting a deadline.
I feel your approach shows a very "Nothing bad will ever happen" attitude.
Yes, of course you should have a backup. Most of those don't run every few minutes, though. Or even every few hours.
"Just trust the backup" feels like a really overkill solution for a system that has, as a core feature, pushing to a remote server. And frankly, a way to justify not using the feature.
I began using Jujutsu as my VCS about 2 months ago. Considering most of my work is on solo projects, I love the extra flexibility and speed of being able to safely fixup recent commits. I also love not having to wrangle the index, stashes, and merges.
`lazyjj` [1] makes it easier to navigate around the change log (aka commit history) with single keypresses. The only workflow it's currently missing for me is `split`.
For the times when I have had to push to a shared git repo, I used the same technique mentioned in the article to prevent making changes to other developer's commits [2].
It's been a seamless transition for me, and I intend to use Jujutsu for years.
Huh, reading the penultimate "“Units of change” and collaboration" section reinforces the feeling that Github PRs really are a poor way to do code submission/review, and have been holding back a lot of the industry from better ways of working for a long time.
GitHub-style PRs are the worst way of reviewing changes, except (in practice, if not in theory) for all the others :P.
When my then-employer first stated using git, I managed to convince them to set up Gerrit -- I had my own personal Gerrit instance running at home, it was great. I think it lasted about a year before the popularity factor kicked in and we switched to GitLab.
At least part of the difficulty is that approximately everyone who would need to learn how to use a new mechanism is already familiar with PRs. And folk new to the industry learn pretty quickly that it's normal and "easy". The cultural inertia is immense, and that means we're much more likely to evolve our way out of the hole (or switch to a completely different paradigm) than we are to en-mass switch to a different way of working that's still Git.
There are ways to make the PR experience more reasonable; GitHub has introduced stacked PRs which queue up. Even without that, I find disabling merge commits puts my team in a sweet spot where even if I'd prefer Gerrit, I'm not tearing my hair out and neither are the rest of my team who (mostly) don't care all that much.
It's maddening, considering their size, and that there are proven other examples out there of versioned change sets and divorcing the commit ID from the content ID.
JJ just surpassed a milestone for me personally where there were no hick-ups for more than 6 months and it feels genuinely superior to git and also to sapling for performance, stability and UX. If you ever considered switching i think it might be now. Colocated mode does not feel like a second class citizen and works really well too so there is always the option to use a sophisticated git client like fork for certain tasks. VisualJJ is also a great albeit not open source vscode extensio n that is slowly catching up to ISL. (If you are used to ISL and think visualJJ looks empty and lacks features: most things are hidden in the context menu which takes some getting used to.)
This article covers my own experience with JJ very accurately. I'll even go as far as to say that if had to write my own article about jj, I'd use exactly the same talking points and examples. Great writeup
I would really love a comparison between JJ and fossil. I use fossil for personal projects instead of git. So I'd like to know if I should consider JJ.
I think the biggest difference is one of philosophy: in Fossil (as I understand it) every change you make to the code is tracked, and there's no concept of rebasing or making the history prettier. If you make a commit and then realise you've left some debug logs in the code, you've got to make a new commit to delete those logs. This has the advantage that you never lose data, but the disadvantage that all of those commits end up in your long-term project history, making things like bisecting or blaming more difficult.
In JJ, however, changes are explicitly mutable, which means if you finish a commit and then realise you want to go can and change something, you've got multiple ways to squash your fix directly into the original commit. In addition to this, you also have a local history of every change you make to your project (including rewriting existing commits) so you still never lose any data locally. However, when you finish adjusting your commits and push them to someone else's machine (e.g. GitHub), they will only get the finished commits, and not the full history of how you developed them.
I know some Fossil people feel very strongly that any sort of commit rewriting should be banned and avoided at all costs, because it creates a false history - you can use Jujutsu like that, but it probably doesn't add much over Fossil at the point. But in practice, I think Jujutsu strikes the right balance between allowing you to craft the perfect patch/PR to get a clean history, and preventing you from losing any data while you do that.
Nice writeup -- had been wondering about how it compares to Git (and any killer features) from the perspective of someone who has used it for a while. Conflict markers seems like the biggest one to me -- rebase workflows between superficially divergent branches has always had sharp edges in Git. It's never impossible, but it's enough of a pain that I have wondered if there's a better way.
For me, it's not so much that jj has any specific killer feature, it's that it has a fewer number of more orthogonal primitives. This means I can do more, with less. Simpler, but also more powerful, somehow.
In some ways, this means jj has less features than git. For example, jj doesn't have an index. But that's not because you can't do what the index does for you, the index is just a commit, like any other. This means you can bring to bear your full set of tools for working on commits to the index, rather than it being its own special feature. It also means that commands don't need certain "and do this to the index" flags, making them simpler overall, while retaining the same power.
Like the author, I'd appreciate a stacked PRs approach, integrated with GitHub (unfortunately). E.g. `a → b → c → d` where I have PRs open for `b`, `c` and not yet on `d`, that are "linked" to the jj changes. So 1 change per PR or it could even be multiple. I've lately become a huge fan of git-spice, that just works.
Stacked PRs is clearly a nice workflow... except that forges like Github generally have really poor support for them.
I wish Github would just allow you to say "this PR depends on this other PR", and then it wouldn't show commits from the other PR when reviewing the stacked PR.
When I'm developing I inevitably fix one thing after another as I pass through the code. What I'd like is a tool to take such PRs and automatically split it up into loosely coupled, cohesive chunks.
With jj, I often do this and use jj split -i, which opens an interactive editor (similar to git's interactive add/rebase) which I can select parts of the change to be split into a separate change/commit. This enables me to take a large piece of work, split it into individual chunks, and open PRs for each change.
I'm not sure you'd call it "automatic" though. Were you thinking of using an LLM to split the commits with some semantic awareness? It doesn't do that!
> You don’t need to explicitly tell jj about what you’ve done in your working copy, it’s already tracked. This removes the need for an “index” or staging area
Does this mean that you have to proactively remember and undo any config file changes you made e.g. while fixing an issue in a test environment? Sounds a little risky.
As others have pointed out, gitignore exists and you should try and build your configuration so that most of the time you're changing gitignored files rather than checked in ones. That said, you can do some pretty cool stuff with Jujutsu in this regard. Because changes are always automatically rebased, you can often create the change that will ultimately become your final commit, then create a new change where you edit any config you need to, then a change on top of that that's basically your staging area.
For example, I recently had a set up that looked something like this:
@ w - the checked out change where I'm editing files
|
* x - local config change
|
* y (bookmark 1) - CI config change that sets up a temp test env
|
* z (bookmark 2) - Start of the changes that will eventually get reviewed and merged
With this set up, I could make changes in an environment that included all of the config changes I needed. Then when I was happy with something and wanted to "save my work", I could run `jj squash --to z`, which would squash everything in change w into change z, rebasing everything else automatically on top of that. Then I could run `jj git push`, and this force-pushed the changes at y and z to their separate branches on GitHub. I had a pull request for the branch at z which other people could then review and where all the tests could run. Meanwhile the branch at y had updated the CI config to remove the usual tests and deploy everything to a temporary environment. So each push automatically updated my test env, and updated the pull request with the "clean" changes (i.e. without my config file fiddling).
If I wanted this sort of setup more long term, I'd find some other way to achieve it without relying on Jujutsu, but for ad-hoc cases like this it's absolutely great.
Agreed, and these comments fail to remember you sometimes need code changes to debug. Its nice to stage/unstage code changes, and gitignore won't help u there
I'm not sure if you meant "how" or "why". As for "how", it's done by rebasing 'u' onto 's' and then rebasing 't' and 'v' onto the rebased 'u'.
As for "why", I think it behaves different from what you expected in two ways. The first is that `--revision` rebased only the specified revisions without theirs descendants (there's also `--source` if you want to include the descendants). The other things it that `--after` is a short form of `--insert-after`, so it's designed for inserting commits between other commits. There's also `--before/--insert-before`. There's also `--destination` which is what you expected (perhaps `--onto` would have been a better name for it). You can pass multiple commits to all these arguments, btw (yes, that includes `--destination`, so you can create new merge commits with `jj rebase`). https://jj-vcs.github.io/jj/latest/cli-reference/#jj-rebase has many examples.
I've tried out jj a little bit personally, but without exaggeration I am using git submodules in every single "real" project I'm actually working on, so lacking support for submodules is a complete non-starter for me :/
What I do is I git clone --recursive and then init a colocated repository. JJ will find the submodules and ignore anything in them, it works decently well.
As entrenched as git is, I feel like its only a matter of time until it's dethroned.
The basic workflow is fine. And there are some very powerful features. But then you try find the parent of a given branch and you're left wondering how the f#!@ thats so hard.
It's definitely nit picking. It's probably 85-90% of what you want it to be. But there is definitely enough room for improvement to warrent moving beyond it. I think the workflows and integratoins (github, gitlab, etc.) make it stickier as well. I just dont think we should assume everyone is just going to use git for the next 20+ years.
I think the larger holes in git are its lack of reasonable support for large files and also its inability to keep secrets.
Both relate to the same fundamental problem/feature that a git commit is a hash of its contents, including previous content (via the parent commit, who's hash is part of the input for the child commit hash).
Agreed, I'd just like to complement vibe coding feels like spamming retarded Mr Meeseeks hoping they get get the job done. It's _probabilistic programming_
Skimmed the article so I admittedly can't speak to much to the content of it, but just wanted to give my 2c on working on individual things after spending a lot of time working with a stack-based VCS in mercurial/sapling -- jj felt pretty hard to get used to and after a couple of weeks I gave it up. I think it needs a competitive visualization tool to Interactive Smartlog.
I've settled on using Interactive Git Log in VSCode.
If anything, I think the jujutsu project as it exists today is probably most attractive to people who already know Git quite well. It's hard to articulate why a given VCS design or workflow is pleasant without having some firsthand experience of nontrivial things that went wrong or at least chaotic.
Among the hard work on the jj maintainers' plate is making it just as attractive and intuitive to people without that background.
jj is compatible with git repos while being faster to learn and easier to use. There’s basically no reason for anyone to learn git now that this exists.
jj seems to be the slowly evolving winner of these alternatives. It just does things on top of git that you cannot do by using git directly no matter how well you know git.
git is dumb. it's in the name. it's good enough, but it is not the best possible tool. neither is jj, but it's a damn fine improvement in some workflows.
It's by design. It's quite stable, too. You were probably confused by assuming it works like git, it really doesn't when you're working on a change. It kinda starts looking like git when changes are committed and pushed to master/main.
I've been using jj, but am considering going back to plain git now because all the latest llm tools know git and not jj. I don't recommend spending time on it now.
There are many available paths to learning that don't leave you beholden to what was in training data from ~2021. This attitude will definitely hurt one's opportunities for personal growth and enrichment.
cornstalks|10 months ago
I loathe GitHub PRs because of this. Working at $dayjob the unit of change is the commit, and every commit is reviewed and signed off by at least 1 peer.
And you know what? I love it. Yes, there's some overhead. But I can understand each commit in its entirety. I and my coworkers have caught numerous issues in code with these single-purpose commits of digestible size.
Compare this to GitHub PRs, which tend to be beastly things that are poorly structured (not to mention GitHub's UI only adding to the review problems...) and multipurpose. Reviewing these big PRs with care is just so much harder. People don't care about the commit message, so looking at the git log it's just a mess that's hard to navigate.
epage|10 months ago
I will split up a PR into
- Individual steps a a refactor, especially making any moves their own commits
- I add tests *before* the feature (passing, showing the old behavior)
- The actual fix or feature commit is tiny, with the diff of the tests just demontstrating how behavior changed
This makes it really fast to review a PR because you can see the motivation while only looking at a small change. No jumping around trying to figure out how the pieces fit together.
The main reason I might split up a PR like that is if one piece is likely to generate a discussion before its merged. I then make that a separate PR and as small as possible so we can have a focused discussion.
I hate squash merges.
wtallis|10 months ago
chrishill89|10 months ago
- The real unit of change lives in Git
- The real unit of change lives on some forge
I want it to live in Git.
Scrutiny6707|10 months ago
Respectfully, that's the dumbest thing I've ever heard.
Work on your feature until it's done, on a branch. And then when you're ready, have the branch reviewed. Then squash the branch when merging it, so it becomes 1 commit.
Commit and push often, on branches, and squash when merging. Don't review commits, review the PR.
I've had people at various jobs accidentally delete a directory, effectively losing all their progress, sometimes weeks worth of work. I've experienced laptops being stolen.
If I used your system, over the years me and various colleagues would have lost work irretrievably a few times now, potentially sinking a startup due to not hitting a deadline.
I feel your approach shows a very "Nothing bad will ever happen" attitude.
Yes, of course you should have a backup. Most of those don't run every few minutes, though. Or even every few hours.
"Just trust the backup" feels like a really overkill solution for a system that has, as a core feature, pushing to a remote server. And frankly, a way to justify not using the feature.
EnPissant|10 months ago
stavros|10 months ago
nonethewiser|10 months ago
2freedi|10 months ago
`lazyjj` [1] makes it easier to navigate around the change log (aka commit history) with single keypresses. The only workflow it's currently missing for me is `split`.
For the times when I have had to push to a shared git repo, I used the same technique mentioned in the article to prevent making changes to other developer's commits [2].
It's been a seamless transition for me, and I intend to use Jujutsu for years.
[1] https://github.com/Cretezy/lazyjj [2] https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-...
nchmy|10 months ago
https://github.com/idursun/jjui
CraftThatBlock|10 months ago
KwanEsq|10 months ago
andrewaylett|10 months ago
When my then-employer first stated using git, I managed to convince them to set up Gerrit -- I had my own personal Gerrit instance running at home, it was great. I think it lasted about a year before the popularity factor kicked in and we switched to GitLab.
At least part of the difficulty is that approximately everyone who would need to learn how to use a new mechanism is already familiar with PRs. And folk new to the industry learn pretty quickly that it's normal and "easy". The cultural inertia is immense, and that means we're much more likely to evolve our way out of the hole (or switch to a completely different paradigm) than we are to en-mass switch to a different way of working that's still Git.
There are ways to make the PR experience more reasonable; GitHub has introduced stacked PRs which queue up. Even without that, I find disabling merge commits puts my team in a sweet spot where even if I'd prefer Gerrit, I'm not tearing my hair out and neither are the rest of my team who (mostly) don't care all that much.
dorian-graph|10 months ago
jFriedensreich|10 months ago
cole_|10 months ago
hdjrudni|10 months ago
Gross.
esafak|10 months ago
renerick|10 months ago
account-5|10 months ago
MrJohz|10 months ago
In JJ, however, changes are explicitly mutable, which means if you finish a commit and then realise you want to go can and change something, you've got multiple ways to squash your fix directly into the original commit. In addition to this, you also have a local history of every change you make to your project (including rewriting existing commits) so you still never lose any data locally. However, when you finish adjusting your commits and push them to someone else's machine (e.g. GitHub), they will only get the finished commits, and not the full history of how you developed them.
I know some Fossil people feel very strongly that any sort of commit rewriting should be banned and avoided at all costs, because it creates a false history - you can use Jujutsu like that, but it probably doesn't add much over Fossil at the point. But in practice, I think Jujutsu strikes the right balance between allowing you to craft the perfect patch/PR to get a clean history, and preventing you from losing any data while you do that.
graemep|10 months ago
JJ itself uses Github, where as fossil is very easy to self-host including issue tracking, wiki etc.
yowlingcat|10 months ago
steveklabnik|10 months ago
In some ways, this means jj has less features than git. For example, jj doesn't have an index. But that's not because you can't do what the index does for you, the index is just a commit, like any other. This means you can bring to bear your full set of tools for working on commits to the index, rather than it being its own special feature. It also means that commands don't need certain "and do this to the index" flags, making them simpler overall, while retaining the same power.
dorian-graph|10 months ago
IshKebab|10 months ago
I wish Github would just allow you to say "this PR depends on this other PR", and then it wouldn't show commits from the other PR when reviewing the stacked PR.
But as far as I know you can't do that.
Munksgaard|10 months ago
esafak|10 months ago
CraftThatBlock|10 months ago
tome|10 months ago
I'm not sure you'd call it "automatic" though. Were you thinking of using an LLM to split the commits with some semantic awareness? It doesn't do that!
n4r9|10 months ago
Does this mean that you have to proactively remember and undo any config file changes you made e.g. while fixing an issue in a test environment? Sounds a little risky.
MrJohz|10 months ago
For example, I recently had a set up that looked something like this:
With this set up, I could make changes in an environment that included all of the config changes I needed. Then when I was happy with something and wanted to "save my work", I could run `jj squash --to z`, which would squash everything in change w into change z, rebasing everything else automatically on top of that. Then I could run `jj git push`, and this force-pushed the changes at y and z to their separate branches on GitHub. I had a pull request for the branch at z which other people could then review and where all the tests could run. Meanwhile the branch at y had updated the CI config to remove the usual tests and deploy everything to a temporary environment. So each push automatically updated my test env, and updated the pull request with the "clean" changes (i.e. without my config file fiddling).If I wanted this sort of setup more long term, I'd find some other way to achieve it without relying on Jujutsu, but for ad-hoc cases like this it's absolutely great.
ramon156|10 months ago
arccy|10 months ago
what you should have is support for local, gitignore-able config files
hdjrudni|10 months ago
How did t end up after u?
I'd expect that to fork into (s -> t) and (s -> u -> v). Either that or maybe (s -> t -> v) and (s -> u).
martinvonz|10 months ago
As for "why", I think it behaves different from what you expected in two ways. The first is that `--revision` rebased only the specified revisions without theirs descendants (there's also `--source` if you want to include the descendants). The other things it that `--after` is a short form of `--insert-after`, so it's designed for inserting commits between other commits. There's also `--before/--insert-before`. There's also `--destination` which is what you expected (perhaps `--onto` would have been a better name for it). You can pass multiple commits to all these arguments, btw (yes, that includes `--destination`, so you can create new merge commits with `jj rebase`). https://jj-vcs.github.io/jj/latest/cli-reference/#jj-rebase has many examples.
manchmalscott|10 months ago
nasso_dev|10 months ago
nonethewiser|10 months ago
The basic workflow is fine. And there are some very powerful features. But then you try find the parent of a given branch and you're left wondering how the f#!@ thats so hard.
It's definitely nit picking. It's probably 85-90% of what you want it to be. But there is definitely enough room for improvement to warrent moving beyond it. I think the workflows and integratoins (github, gitlab, etc.) make it stickier as well. I just dont think we should assume everyone is just going to use git for the next 20+ years.
actinium226|10 months ago
Both relate to the same fundamental problem/feature that a git commit is a hash of its contents, including previous content (via the parent commit, who's hash is part of the input for the child commit hash).
matkoniecz|10 months ago
what you mean by that?
k__|10 months ago
Would be nice, if Codespaces keeps the JJ change stored somewhere, so it isn't tied to the lifetime of the machine.
jffhn|10 months ago
I often see programming as going MMA with data, until you get what you want out of it.
Using brute force algorithms is going berserk, vibe coding is spamming summons in hope they get the job done, etc.
az09mugen|10 months ago
hbay|10 months ago
I've settled on using Interactive Git Log in VSCode.
sashank_1509|10 months ago
Valodim|10 months ago
dijit|10 months ago
unknown|10 months ago
[deleted]
abcd_f|10 months ago
FridgeSeal|10 months ago
unknown|10 months ago
[deleted]
sigmonsays|10 months ago
I feel like people should just learn to use git.
mtndew4brkfst|10 months ago
Among the hard work on the jj maintainers' plate is making it just as attractive and intuitive to people without that background.
shandor|10 months ago
I’ve been using git for 15+ years,and definitely share the sentiment that basically everyone needs to learn it well as it is just that entrenched.
…but I love that people are coming up with potential alternatives that have a meaningful chance of moving the whole VCS space forward.
medler|9 months ago
jFriedensreich|10 months ago
baq|10 months ago
cadamsdotcom|10 months ago
Why not just merge it into git?
wmf|10 months ago
And I'm sure there are some people who actually prefer Sapling or raw git.
matkoniecz|10 months ago
and deal with more abstractions layers if anything goes wrong
> It’s clear from multiple posts over years that jj has a better UX than plain git.
it is clear that multiple people like it
not that UX is strictly superior
theusus|10 months ago
baq|10 months ago
aiiizzz|10 months ago
mtndew4brkfst|10 months ago