Jujutsu: a new, Git-compatible version control system [LWN.net]
Posted by Alexander_Selkirk@reddit | programming | View on Reddit | 41 comments
Posted by Alexander_Selkirk@reddit | programming | View on Reddit | 41 comments
behind-UDFj-39546284@reddit
What's wrong with the index?
steveklabnik1@reddit
So, I loved git's index. I was very skeptical of this when I tried jj. Now, I won't go back.
This isn't really the case. The thing is, the index is just kind of weird in that it's an additional concept, an additional state for everything to be in. What jj does is unify the concept of an index with regular old commits. This means that instead of needing additional flags to commands to determine what happens with the index when you run them, you instead just use the normal tools for commits.
Let me explain in practical terms, with what I am literally doing right now. I am currently writing some "convert this HTTP response to this structured output" kind of c code. Here's my
jj log:This output is slightly different than
git logbut hopefully it makes sense. There's a lot of colors in the CLI that helps stuff stand out, which gets a bit lost here, oh well. as you can see, I'm currently on an empty commit. Its parent is the commit I'm actively working on: I have a pull request out, hence the auto-generated branch names (I don't care about branch names any more with jj but we're talking about the index now, so that's fine. The parent of that commit istrunk, aka my main branch upstream. (I've enjoyedtrunkmore thanmasterormainlately, so that's what I use.)Okay, so I add some new code. I then
jj logagain:It now doesn't say the change is empty, but that it doesn't have a message yet. Sure. Let's look at what's changed:
I removed some of the diff, because it's not important. Cool, I added a line. I want to now move that from
@, my "index" if you will, to its parent, the actual commit I'm working on. That'sjj squash:I've moved the diff from
@into its parent, and now you can see my "index" is empty again, and there's a*on the branch, indicating that I've modified the commit so now it's not matching what's on my remote, so I need to push.I did this because I am doing a "rebase changes into the commit" approach, but if you wanted to add this change as its new own commit, all you'd have to do is
jj describeto give it a message, and thenjj newto build a new "index" empty commit on top of it. Before you push, you'd want to update the branch withjj branch set trunk -r @-, becausejj's bookmarks don't auto-advance like git's branches.Okay so if it's the same, what's the advantage here? Well, squashing isn't the only thing I can do: I can do any arbitrary stuff I could do with normal commits. For example, if I wanted to add the commit, instead of describe + new, I could
jj split, which splits a commit into two, choosing to keep all changes in the parent and an empty new commit on top of it. Or put some changes in one and some in another. Really, anything you want.There are also some interesting downstream effects, for example, rebasing is entirely in-memory in
jjthanks to this, which means it's very fast. But the important bit is that it lets you do the same thing as git does, but more.Master-Chocolate1420@reddit
sorry for late-ping, but isn't this friction largely eased with git frontends like lazygit or tig?
I'm new trying out jj, while it looks really good with delt diff and customization.
commiting, setting bookmark that -r @- command gets long enough.
then again we have jjui, which is frontend for jj.
It additionally annoys me when i have to tag certain commits and push them.
tbh,I have not tried jj split yet, neither I tried that on git; that bit seems interesting, but also verymuch complicated.
steveklabnik1@reddit
Those can help with some workflows, sure, but they still don’t do all of what jj does. Also there’s been an advance bookmarks command thing added since I wrote this, I forget what it is since I don’t work this way.
dalekman1234@reddit
Loved your talk(s) / work on rust back in the day. Thanks for all you do!
steveklabnik1@reddit
Thank you!
steveklabnik1@reddit
Thank you!
Alexander_Selkirk@reddit (OP)
It adds complexity, and perhaps, depending on the specific use, more comolefity than is needed.
Very abreviated, instead of, say, N commands that operate on commits, including the commit that represents the worktree, you need N commands that operate between commits (like merge, rebase), N commands that move stuff between index and worktree (like add, reset), and N commands that interact worktree and history (like checkout, reset, commit , switch).
And on top of that comes state when doing rebases, for example.
behind-UDFj-39546284@reddit
I don't think that having N commands for this or that is what the index must be blamed for. I kind of agree that git has inconsistent commands hence it makes it more difficult to use, but git has a rich set of low-level commands you can build your high-level ones on top of. Sure if you understand what git is. If it's too hard, script even -more-high-level commands on top of the high level ones despite porcelain commands are not meant to be scripted as many others think Anyway.
I do believe the index is a right tool: it just tells me what I'm about to commit and what isn't going to be a part of it. Not either all or nothing. That's the key point. I don't even use a GUI front-end for git, but I'm very comfortable with adding or checking out text hunks with the interactive option set. Whenever I resolve conflicts I clearly see what I must resolve and I can change things unrelated to the conflicts and still be confident that I don't conclude the merge if there is something to be added or discarded. The same for cherry-picking and whatever using it (revert which is merely an undo, nothing special; rebase which is just an advanced cherry-picking script) when it goes to the user interaction with index. And there are more.
I was "lucky" enough to commit hence publish everything by accident in Subversion. It's nightmare. Can't recall how Mercurial handles that having no index either. I really love to control what I want to be committed.
forrestthewoods@reddit
Nah. The stage is just a fundamentally bad and unnecessary idea.
It’s not that it’s too hard. It’s that the index/stage is simply a bad idea.
Alexander_Selkirk@reddit (OP)
The thing is, in jj, there is a working commit, which is a combination of both the index and the worktree.
If you create a new file, it gets added automatically to the working commit.
If it is in the .gitignores list, it is not added to the working commit.
If you remove it using "jj file untrack", it is removed from the working commit (and a new working commit is created).
Same with changes to tracked files.
jujutsu does not automatically publish stuff, it only publishes what you add a bookmark (git branch name) for, and push. Before you push, everything is private.
I was also skeptical about the automatic addition workflow, but it turns out that it becomes much easier to try out and explore things. One can do several intermediate commits and consolidates them into a final commit which only contains what one wants.
Of course, different people do have different needs, and some people can keep all of git in their head and just use the appropiate magic command once the situation for it arises. In the same way as some people can keep all of C++ in their head, and use all its complexity. And other people do not like the added complexity of C++ and use perhaps C instead, or Rust if they need Unicode support and a lot of data on the heap and lifetime and ownership tracking (which you can do all perfectly in C++ as well, you just need to keep more stuff in your head, and in some's peoples head is space for seven things or so, and other's people had can keep 100 or 1000 things like all git commands and their options).
I am fine with both of it, I just prefer simpler things when they do what I need.
Big_Combination9890@reddit
And what if I don't want that to happen?
Removing the index doesn't remove complexity, it just shuffles it elsewhere. There are countless scenarios where I WANT changed code in my worktree, but not include it in my next commit.
That's what the index delivers. Removing that with an automagical mechanism may make some simple workflows easier (at least for beginners), but makes a whole lot of other stuff harder.
steveklabnik1@reddit
This is now configurable, you can turn it off if you want.
It unifies it with commits, which means you get more tools for working with it.
This is absolutely the norm in jj as well. That is, your worktree is at
@, but you're building up a commit at@-, that is, the parent.I loved git's index. JJ gives me the same power plus some by not having it.
Big_Combination9890@reddit
So I get the same functionality, at pretty much the same complexity, and all I have to do to end up exactly where I started, is learn an entirely new tool, that less people know and use and that is less battle tested and integrated than what I have now.
Sorry, maybe I missed it, but where exactly is the value proposition here?
Alexander_Selkirk@reddit (OP)
It does not remove, but reduces complexity because all the commands and operations that work on earlier commits, also work on files and changes that just have been added. This works by giving you more flexibility: In git, you can for example add and remove files from the last set of files, or fuse the changes in a branch into the last commit of another branch (squash merge). Or you can modify the last commit message. In jj, you can do that with the last set of changes, and with any other commit too.
It is quite a fascinating feat of simplifying an interface, and making it more powerful at the same time.
Alexander_Selkirk@reddit (OP)
If the .gitignore mechanism and associated configuration don't work for you, you just use git, or another tool. If something is essentially a different user interface to an existing system, trying to improve it, it would probably miss the point if it behaves exactly like the old interface.
behind-UDFj-39546284@reddit
No, in the svn case, I only tell that
svn commitwas a nightmare way to publish unfinished work if you were late to press Ctrl-C or clock Cancel. The idea of having an index I control eventually is what I appreciate.If I understand you about adding the files automatically, does it also mean that whenever I create a temporary file, say
foo > bar.txt" (in the work dir, bar.txt is not a .gitignored file, why would it be?) for temporary needs like to open it in an editor and do some work, in jj I have to jj-untrack it or explicitlyrmit? If so, I really like the git way "add first". The jj way, I guess but not sure, can be sort of mimicked in git using the git-pre-commit hook to git-add everything to the index except explicitly untracked files whose custom list file resides somewhere in the .git dir (the latter can be even not needed if usinggit-update-index --assume-unchaned` if I'm not mistaken).Alexander_Selkirk@reddit (OP)
Ah, and one more thing, when you are working on a feature branch and push new commits and a new branch pointer to a git remote, and you find that there is something that was pretty stupid and should not be there, then you can remove or rewrite your commits, and push the branch again. The previous commits will become orphaned and later garbage collected. This is technically more or less the same as rebasing a local branch on master and pushing it.
Of course, you won't rewrite merged branches which other people are already basing their work on, regardless whether you use git or jj or whatever other user interface. And usually, the master branch of a public repo is protected from such rewriting of commits, to prevent data loss and loss of productivity.
Alexander_Selkirk@reddit (OP)
Actually, you'd need to add it to .gitignore, and then untrack it.
But you can also store your private Notes in a text file with extension *.txt.local or whatever you want, and at *.txt.local to your .gitignores file.
jujutsu builds a commit from the files you have in your worktree whenever you run a jj command, but that commit is not final, you are kind of "forging" your commit until it is good to be commited and pushed. You just work directly on commits, and edit them. So, if it includes temporary files, compile output, whatever, it is only local and never gets send to the server.
bastardoperator@reddit
Sorry, but fuck that, it’s bad design. Implicitly adding code is the proper way. I don’t see this working with large teams or monorepos at all
steveklabnik1@reddit
jj's being built with google's monorepo in mind. Here's a recent talk about it, with a timestamp to that part: https://youtu.be/LV0JzI8IcCY?list=PLNXkW_le40U6Igw7FcHgQGnQNDQ8kWyuE&t=927
behind-UDFj-39546284@reddit
Uhm, I hope it would also support exclude files from the .git dir in order not to change the shared .gitignore file just because of my personal needs.
The scenario you're describing is like editing commits while rebasing interactively. I may add temporary files to the commit making it dirty until the temporary files are removed from the commits (manually or git-filter-branch-ed). This is of course it's done in an isolated branch and will be sent to the remote because the temporary stuff is committed. The bandwidth suffers, remote space suffers, but I can fetch the temporary files from another machine and continue the work. if I'm getting you right: jj always adds public and private files (temporary for instance) to a commit but only sends public files, or by editing you also mean I must remove temporary stuff myself before I send my commits to the remote repository?
Alexander_Selkirk@reddit (OP)
It does so:
https://martinvonz.github.io/jj/latest/cli-reference/#jj-file-untrack
Alexander_Selkirk@reddit (OP)
Stuff in local commits is not send to any server before you push to that git branch. And before you can do that, you need to update the tip of the git branch to the working commit, which does not happen automatically.
Alexander_Selkirk@reddit (OP)
And to add, this is one of the many ways where jujutsu optimizes for the frequent case (you add a new source file, and it is automatically tracked), on cost of an unusual case (during development, you generate a new type of file which is not yet in your .gitignore, and which you do not want to track).
teerre@reddit
jujutsu is a no brainer. Anyone doubting should just try it for a bit. It's vastly superior to git on the one thing that matters: cognitive overload
Unfortunately it's one of those situations where the dominanant option, despite worse, has a lot of power. I know jj integrates with git, but when your whole team or company aren't working on the same style, many of the advantages go out of the window
The fact that you often need to again convert it back to the git way to see it on some website related to the vcs also hurts a lot
To no fault of jj itself, unless we have jujuhub, it's a hard sell for teams. Love using it on my own though!
Alexander_Selkirk@reddit (OP)
I really do not understand your comment.
jj works on git repositories. When you need code-signing in a specific way, you can use git on the repo. When you have a brittle ssh-over-vpn situation, you can use git to push to the remote, after updating the local git branch. When you need to ingest email patches for your TempleOS fork, you can use git. And continue adding changes with jj after that.
It is correct that there are different git workflows and methods, like gitflow, and rebasing. This is something teams need to agree on, and jj will not change that.
In fact, nobody ever needs to know that you use jj instead of git.
I think one can say that jj makes rebase operations easier, and that can be a win for larger teams, since changes are easier to review. (But this does not substitute testing every relevant commit, if you need bisect to work.)
Still, you can always rebase your unpublished private branch, and tidy it up.
In the worst case, that can cause team members to take a bit more time before they push their stuff, with the win that their changes are more tidy and more logical. I do not see any problems with that.
teerre@reddit
The problem isn't with git itself, it's with github/lab/similar. If it were to be translated to github, it would be more akin to stacked PRs, but that's not how most people use github, therefore I need to keep thinking in a git way even though I'm using jj, which defeats the purpose
Alexander_Selkirk@reddit (OP)
You can still use rebasing for your local branches, which is also where alle these features are most useful - since you won't anyway edit history of a public, merged branch.
teerre@reddit
Well, of course I can, but it's ungood. The whole point of jujutsu is reducing the cognitive load. The cognitive load of using jujutsu and then thinking how it will look in git is bigger than just not using jujutsu
Alexander_Selkirk@reddit (OP)
Personally, I have been using Emacs/Magit for a long time. It makes it easy to divide large and complex changes into meaningful commit pieces. What is not so easy, is rebasing, I use it rarely and I keep forgetting the details.
jujutsu seems to make that easier, since one is essentially editing the commit graph with a very small set of commands. For example, a merge commit is just a new change with not one but several parents. And rebasing means just navigating to a change, editing it, and then resolve conflic markers.
A few days ago, I had to find a bug triggered by a test. This needed some amount of experimentation and several tries to narrow it down, while still producing the bug. That was easy to do, be creating a set of stacked commits with successive tweaks and deletions, in several unnamed branches, and then collapsing the right set of changes into one commit, usund the squash and abandon commands of jj. This is of course also possible with Magit, but I would not have done it, because of higher friction.
With jj, there are still rough edges, for example when using a github remote with ssh keys and VPN. But in these cases, one can set the branch visible to git (jj bookmark move@git, and then push using git as normal.
Another thing I had to learn was how to remove generated files, because these are added automarically to the working commit: adding their names to .gitignore, and then use "jj file untrack.
Some git greybeards say that git has already the best possible interface, and newer UIs would necessarily have less power. But this is, allow me the pun, rewriting history: When git came out originally, its first author wrote about "plumbing", parts which do low-level work, and "porcelain", which presents the user interface, and expressed that this porcelain can be adapted to the user. Magit is such a porcelain (it says so in its documentation page). Jujutsu is another one.
Alexander_Selkirk@reddit (OP)
And by the way, jujutsu is now included in Arch Linux. It can also easily installe with Rust's cargo command - see Steve Klabniks nice jujutsu tutorial for specifics. (Note that Steves tutorials describes version 0.15, while the current version is 0.22, and has replaced the "jj branch" commands with "jj bookmark".)
steveklabnik1@reddit
Thanks for linking, I do need to find the time to finish v2...
Alexander_Selkirk@reddit (OP)
Oh, many thanks for your thoughtful writing and for your inspiration! It is not only enjoyable reading but also very helpful!
steveklabnik1@reddit
Thanks!
Deathnote_Blockchain@reddit
I appreciate the way you spelled it.
FistBus2786@reddit
Would have preferred jūjutsu but hey I'm not here to nitpick.
Deathnote_Blockchain@reddit
Yeah most English speakers on ansi keyboards can't find macrons so I am not gonna give us a hard time about that
Worth_Trust_3825@reddit
New git compatible vcs that removes index? How original.
Alexander_Selkirk@reddit (OP)
I think the central point is that you can organize and modify changes in files that you just did create in exactly the same way as any changes earlier in the change hisory. For example, for moving a set of changes to another commit, you can use the "squash" command - regardless whether this is in the last file you saved, or N steps earlier. This unification makes the operations on your graph of changes more powerful. And, the most recently created changes are not handled any differently than the older changes. For example, you can pick "hunks" in git or magit with the interactive option ("-i"). In jj, you can use this option both with the squash command, or with the move commands, which lets you reorder commits or parts of them.
And that also means, if you forgot to remove an auto-generated file or a shell snippet, you can do that in the last captured set of changes, or in any earlier commit - with the same command. Or, you can add a file that you had forgotten. Or, you add a doc string, and twenty minutes later you see a typo in that doc string, and then you just navigate to the change that added the doc string, insert the fix for the typo, and fuse these two commits, and after that you continue adding new commits.
All this is possible with git, but jj makes that effortless.
notoriouslyfastsloth@reddit
i'm getting the JuHub.com domain first