jujutsu v0.42.0 released
39 points by mediremi
39 points by mediremi
jj git fetchnow generates evolution history based on change IDs. If change IDs are preserved by the remote, local descendant revisions will be rebased onto the rewritten parents.
Sounds like this might mean I no longer will need to do jj new main right after every jj git fetch? (Assuming the remotes I work with can preserve that change ID.)
If so that's a nice QoL change.
That’s not how I read it: presumably, when you fetch, you’ll have a different change on main then you did before, so this won’t help with that.
That is my understanding as well. I think it only automatically rebases if the commit you created a revision off of gets rewritten (the change id is the same). If main gets new commits, then the change id representing the tip of main changes, so you still need to do jj new main
for the issue you describe I ended up writing an alias (pulln = pull + new):
pulln = ["util", "exec", "--", "bash", "-c", """
[[ -n "$(jj log -r 'latest(::@ & ~empty()) & trunk()')" ]] && on_trunk=1 || on_trunk=0
jj git fetch
[[ "$on_trunk" == "1" ]] && jj new -r 'trunk()'
""", ""]
Now that I look at it, I can see it is far from being optimized but I'll post something that I know to work over untested optimizations written on my phone
If you add the trailer to the commit message, any remote will preserve that. Although no idea what happens with GitHub generated merges here, that don't have change IDs...
The trailer is for Gerrit change IDs though, not the Jujutsu change IDs. But as noted in the docs, "GitHub and other major forges seem to preserve them for the most part".
I was under the impression that JJ falls back to trailers parsing if the header is missing. Did I imagine it?
Hm, if that's the case I can't find it in the docs. But I also doubt it since Gerrit change IDs are 20 bytes while Jujutsu's are only 16?
You're right. That's a bit disappointing. I wish it did parse a trailer like that :(
There's been talk between jj <-> gerrit <-> git butler to unify this stuff, but it's going to take a while.
Switched to the mimalloc memory allocator for better multi-threaded performance.
Any more details on it? I've been tweaking around this area and switching e.g. to jemalloc for long running processes to help with fragmentation, but in my experience jj starts in 1ms and is done in <10ms, so I'm surprised allocator would make noticeable difference.
Edit: https://github.com/jj-vcs/jj/pull/9484 PR . I only found https://github.com/jj-vcs/jj/issues/2490#issuecomment-2595323515 , which doesn't seems like big speedup, but faster is faster and it's just a couple of lines to change anyway.
Can anyone provide me with some reading (or watching) on why one might use jj over standard git (I'm aware jj can wrap over git)? I've experimented with it in some hobby projects, but struggled to really find the magic reason why it's better or easier.
jj new) as soon as you start working on something new and don't need to do write commit descriptions until you're ready to. you can also get back to previous changes with jj edit to continue working on themjj status) snapshots the working tree an stores it in jj repo. so nothing is lost and nothing leaks into another changeI wrote more things here: https://thisalex.com/posts/2025-04-20/ but that was more than a year ago already
Thank you for the link, I shall read it later.
Regarding those points, aren't they effectively handled in git? You can just rename a branch trivially whenever, so you don't need to fret with naming and rebasing allows ordering, commit message altering etc? This sounds like its more of a UI problem rather than anything else?
If change IDs are preserved by the remote
are they usually? I’m not sure in which form jj pushes change IDs. I know jj gerrit upload adds change id footers, but normal jj git push doesn’t.
In my experience, as long as nothing rewrites the commit, it's guaranteed to be preserved. Any thing like squash merge or rebase merge on GitHub does not, though, as it rewrites the commit. If they use standard libgit2-based tooling to do it, custom headers are not preserved. Some other tooling (like the Rust libs used in GitButler) will support preserving custom headers but I doubt any forges are using these.
GitHub at least uses git replay not rebase, so it does preserve it. And I know it’s on the minds of some of the open source forge’s maintainers.
TIL git replay preserves custom headers. I'm on my phone so will have to look more deeply another time, but when I worked on a PR adding custom header support to JOSH I found that the libgit code all ignored custom headers. Did git replay use a different backend or has that just changed since I looked, I wonder...
(I may also be misremembering but I definitely was knee-deep in libgit C at some point during the investigation phase)
There is a chance I’m misremembering the implementation too, but from the other comments in this thread it seems like it’s preserving them regardless of how.
Yes I'm wondering about this too. What remotes preserve change IDs? How do I know if that's happening correctly or not?
If you use git cat-file commit HEAD (or another ref instead of HEAD) you'll see the headers:
% git cat-file commit HEAD
tree 1d98711d495b1bfca9a27dce6bd5fe0751e9788d
parent 759a4e2e83ebf03d8ee7b86911e583fc060dbc1a
author Steve Klabnik <steve@steveklabnik.com> 1780551358 -0700
change-id yktoxulrtuvwuxqzuxoxstuwnnvqzsyw
if that change-id line isn't there, then it wasn't preserved.
The real issue isn't so much about remotes, it's that some git commands preserve headers and some destroy them. So it really depends on how the remote is implemented, and what it does to manipulate the repository.
The real issue isn't so much about remotes, it's that some git commands preserve headers and some destroy them
I would put it as “don’t (preserve them)” rather than “destroy them”: iirc from my messing about with git preserving headers generally require explicitly extracting and forwarding the extra headers, and depending on the command that’s necessarily not obvious e.g. rebase does not definitionally map commits 1:1.
I can say that Gitlab seems to be doing it. I have just git cat-file... on a remote bookmark I started tracking right now and it prints commit id, change id, and the right commit description.
Edit: Github does seems to do it. Lobste.rs source code is hosted on Github, and pushcx works with jj. If you check one of his commits, you wil find the change id.
The change ID referred to there is not the Gerrit change ID (which you'd see in the commit trailers/footer), but the jujutsu change ID (I assume).
well, jj gerrit upload uses jj change ids as gerrit change ids.
Change IDs are stored in git commit headers as reverse hex encodings. This is a non-standard header and is not preserved by all git tooling. For example, the header is preserved by a git commit --amend, but is not preserved through a rebase operation. GitHub and other major forges seem to preserve them for the most part. The header is written by default in commits created by jj (since version 0.30.0) and this functionality can be turned off using the git.write-change-id-header setting.
the docs answer this question, thanks!
The docs mention that the generated Gerrit change ID is not the same though:
Note: The Gerrit commit Id may not match that of your local commit Id, since we add a
Change-Idfooter to the commit message if one does not already exist. This ID is based off the jj Change-Id, but is not the same.
are they usually?
As I mentioned in another comment, Gitlab does. Using it at work right now. And Github too; you can check pushcx commits (or mine) to lobsters codebase there.