A better merge workflow with Jujutsu
ofcr.se
ofcr.se
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)
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.
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?
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`.
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?
[1] https://github.com/martinvonz/jj?tab=readme-ov-file#status
How much indispensable, in dollars?
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.
(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.)
> path filtering (`git log -- <PATH>`)
This already works today with `jj log`.
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.
Regarding rename detection, it seems like that is actively being worked on, which is really encouraging! https://github.com/martinvonz/jj/pull/3574
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
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.
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 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.
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.
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.
I feel like the best terms are patch id and patch revision id.
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.
Oh well, naming things is hard.
- "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.
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).
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.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 don't use collocated repositories, so I can't speak to your second question, but I rarely hear people complain about it on their Discord.
> 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.
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.
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.
If you're a developer, for almost every job or open source job you'll be required to use git, but I think most people long of moving away from it
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.
> 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.
This is definitely me. I will never understand it.
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.
a) you almost always work in the same repo; or B) you are in charge of the repos you work in.
Often these are not true for me. I have no deal with other people's repos, dealing with merge or rebase depending on their conventions etc.
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.
> The index is a useful tool
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.
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".
$ 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.
> ??? How is this a disaster?
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.
I wish you couldn't!
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.
That was also a time when the entire software development industry numbered at most 50,000 globally and was still a nascent industry.
> 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...
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!
Please let me know the next thing in advance ;)
... 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.
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. :)
I would rather use mercurial.
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.
It's not quite clear to me how much simpler DVCS concepts can be.