You can do that with just `jj split` too. The FAQ entry you linked to is for when you accidentally amended a commit and now you want to restore the bookmark to the old commit and move the changes you amended into a new commit on top instead.
I have used both Git and jj. I find it easier in jj.
`git reset` by itself doesn't split a commit AFAIK. You need to then `git add -p` and `git commit` (and recover the commit message from the old commit). And what happens if you had other changes in the working copy first? Or if you want to split a commit that's not at HEAD?
> `git reset` by itself doesn't split a commit AFAIK.
Git reset splits a single commit, into two "things", another commit with the first part and a second part that is put into a version prepared for a commit (--soft), prepared for further editing (--mixed) or thrown away (--hard). To me that counts as commit splitting, but it may not match with JJ terms. Also splitting into two commits with the same commit message doesn't sound quite useful to me, so the default of Git two require a second commit message is sensible to me.
Correct me I'm I'm wrong but I think were talking about using `git reset HEAD^` for splitting a commit. That will move the current branch backwards one step. With `--mixed`, it will also move the index back one step, so the index is empty (relative to HEAD) and the working copy has the combination of changes that were in the previous HEAD commit and the working copy (relative to the previous HEAD). I think that's more like squashing than splitting because we have fewer "things" after: we have one commit fewer (the previous HEAD commit may of course still be reachable from another branch, in which case we still have the same number of "things" afterwards). It's similar with `--soft` and `--hard`, except that the old changes end up in a different "thing".
At a less technical level, the point of splitting a commit is to end up with some of the changes from one commit in a new commit and the rest of the changes in another commit. That's what meant when I said "`git reset` by itself doesn't split a commit", because you need to do something like this:
git reset HEAD^
git add -p
git commit -C HEAD@{1}
> Ok, but then what was your point?
Just that additional steps are needed.
For example, if you wanted to split the HEAD commit but you had already started working on a new commit so have some changes in the working copy, then you might instead have to do something like this:
git commit -m tmp
git rebase -i HEAD^^ # Use the "edit" action for the first commit
git add -p
git commit -m first
git rebase --continue
git reset HEAD^
The other case I mentioned was if you want to split a commit on another branch. Then you have to insert some additional commands in that sequence. As I said, I know how to do this with Git. I just find it easier to do with jj, where it's `jj split -r xyz` to split commit xyz, whether it's the current working copy, an ancestor commit, or on an unrelated branch.
(Take my Git commands with a grain of salt because I haven't used Git in 10 years.)
> Correct me I'm I'm wrong but I think were talking about using `git reset HEAD^` for splitting a commit.
I wasn't. I wanted to do the same as in the FAQ entry we are talking about, so I wanted to reset it to an older commit representing the same change (i.e. before an amend that we are now reverting). This is likely in a rebase, but we can always rebase later and only do the splitting now.
> With `--mixed`, it will also move the index back one step, so the index is empty (relative to HEAD)
Yes and this is the default (without any flag).
> That's what meant when I said "`git reset` by itself doesn't split a commit", because you need to do something like this:
That's what the `--soft` is for, then `git reset` does not touch the index.
> Just that additional steps are needed.
The only "additional" step required is specifying a commit message, which, as I said earlier, to me is a sensible default.
What I suggested applied on this case would be:
git commit -m tmp
git checkout @~
git reset --soft previous-version # which you get from the reflog
git commit -C @
git rebase @~ branch-you-were-on --onto=@ # not of much use, when you only have a single commit you are throwing away in the next step, but when you are editing something earlier, this is likely what you want.
git reset @~
If you want to do it with rebase:
git commit -m tmp
git rebase @~2 # break after first commit
git reset --soft previous-version
git commit -C @
git rebase --continue
git reset @~
More idiomatic, due to using the global list of todo commits:
git rebase -i @~ --autostash # break after first commit
git reset --soft previous-version
git commit -C @
git rebase --continue
You can drop the rebase, when it is really the commit in HEAD you want to split.
Actually what you can also do, but this doesn't use reset, is this:
git rebase -i @~ --autostash
# add as the first line:
pick previous-version
git rebase --continue
This will even do what you wanted and just reuse the same commit message without asking.
Honestly, what I do most of the time to split commits (when there isn't an older version I want to split it to) is to just amend and then unselect the changes I don't want with the cursor.
Yet another way is to duplicate the commit and apply the partial reverse to the first one. I think this is what matches theory the most, and is what I essentially end up doing.
> There is probably some lingering unfamiliarity with git among jj enthusiasts as well.
I've heard this a few times. But from what I've seen, it seems like often it's the Git enthusiasts who seem to be unfamiliar with jj. I haven't heard from anyone who used jj for a few months and knew it well and then switched to Git.
I have never used jj, nor met anyone who used it in person. Given how new it is, you just won't find enough people who used it at all. I think git is like the vim of VCS systems and like vim there are some people who just don't think that way. I think jj might be more like emacs in that it has the initial appearance of being simple, but when you get into it more deeply you realize that it is not so simple after all.
I have used other VCS systems that made similar claims about being simpler, such as Mercurial. I think I got to be fairly expert at Mercurial before becoming expert at git, and even used it to interact with SVN repositories. (I've also used git for SVN. This is another thing git does better.) After actually learning git past the first few commands, I would never go back to Mercurial. I don't want to mix and match systems either. There are things I want git to do better, but I would not call these simple feature requests... They are more along the lines of advanced features that need to be extended or polished.
Mostly, yes. It also covers changes to the working copy (because jj automatically snapshots changes in the working copy). It's also much easier to use, especially when many refs were updated together. But, to be fair, it's kind of hard to update many refs at once with Git in the first place (there's `git rebase --update-refs` but not much else?), so undoing multiple ref-updates is not as relevant there.
You can specify a commit, yes, but how do you remember your set of unnamed commits? Once HEAD no longer points to a commit, it will not show up in `git log`.
I agree that Git could gain an operation log. I haven't thought much about it but it feels like it could be done in a backwards-compatible way. It sounds like a ton of work, though, especially if it's going to be a transition from having the current ref storage be the source of truth to making the operation log the source of truth.
The last one is always available via `git checkout -` and if you want to do more you can do `git checkout @{4}` etc. . It will also show up in `git log --reflog`. I honestly don't see the problem with naming things. Typing a chosen name is just so much more convenient than looking up the commit hash, even when you only need to type the unique prefix. When I don't want to think of a name yet, I just do "git tag a, b, c, ..."
I also tend to have the builtin GUI log equivalent (gitk) open. This has the behaviour, that no commit vanishes on refresh, even when it isn't on a branch anymore. To stop showing a commit you need to do a hard reload. This automatically puts the commit currently selected into the clipboard selection, so all you need to do is press Insert in the terminal.
> It sounds like a ton of work, though, especially if it's going to be a transition from having the current ref storage be the source of truth to making the operation log the source of truth.
I don't think that needs to be implemented like this. The only thing you need to do is recording the list of commands and program a resolver that outputs the inverse command of any given command.
Yeah but in jj every time you run ‘jj log’ you see all your anonymous branches and you can rebase all of them at once onto main in 1 command.
When I’m exploring a problem I end up with complex tree of many anonymous branches as I try different solutions to a problem and they all show up in my jj log and it’s so easy to refer to them by stable change ids. Often I’ll like part of a solution but not another part so I split a change into 2 commits and branch off the part I like and try something else for the other part. This way of working is not nearly as frictionless with git. A lot of times I do not even bother with editor undo unless it’s just a small amount of undoing because I have this workflow.
Git is to jj like asm is to C: you can do it all with git that you can do with jj but it’s all a lot easier in jj.
I guess I never had complex trees from such an action, just a bunch of parallel branches, but I would say splitting and picking commits from different branches is not exactly hard with git either. Also you can also see them in git, but they won't have change ids of course.
I know how to do everything in git that I can do in jj but the thing is I would never bother doing most of these workflows with git because it’s way more of a pain in the ass than with jj. I work with version control in a totally different way now because how easy jj makes it to edit the graph.
Within a day of switching I was fully up to speed with jj and I never see myself going back. I use colocated repos so I can still use git tools in my editor for blaming and viewing file history.
Sure even rebasing a complex tree in git can be done by creating an octopus merge of all leaf nodes and rebasing with preserve merges but like that’s such a pain.
I'm sure it is stuff that makes sense to a jj user. Since I have not read the manual, it is nonsense to me. I'm just drawing attention to the fact it's a different set of non-obvious terminology and features as compared to git. I'm sure anyone who read the manual for either tool could figure it out. The trouble with git is that people don't read the manual, and hardly try to do anything with it, then loudly complain about it being tricky. Anything as complicated as version control is going to be tricky if you don't read the manual. I don't think making another tool entirely is the right solution. Perhaps a different set of git porcelain tools could help, or some git aliases. Maybe better documentation too. But some people just can't be pleased.
This seems to be a common misconception, that many jj users don't understand Git. Most jj users I know were pretty good at Git as far as I can tell. Perhaps you'll find this recent video where Scott Chacon talks about Jujutsu interesting: https://www.youtube.com/watch?v=PsiXflgIC8Q. Scott is a GitHub cofounder, author of Pro Git, and now runs GitButler.
> I don't think making another tool entirely is the right solution.
I considered making the changes to Git but the changes I wanted to make would make the UX so different that it would basically mean introducing a whole parallel command set to Git. I figured it would take ages to get Git to that state, if I could sell the ideas to the Git community at all. By the way, the video above talks about an proposed `git history` series of commands inspired by Jujutsu (also see https://lore.kernel.org/git/20250819-b4-pks-history-builtin-...).
Well, if I had to guess, many current jj users are git veterans who are tired of watching git noobs struggle. The other segment is the git noobs themselves, who never really bothered to learn git and have a deep aversion to reading the manual and doing basic experiments. Just a guess, though.
I think I saw Scott Chacon talk about his git config file and advanced git features. Whoever it was, it mentioned GitButler. That was a good talk. I would certainly expect someone like that to have a lot of interest and expertise in git. But it seem to me that there is also a potential commercial angle to making a new/alternative VCS.
I looked at the mailing list entry you linked to about `git history` commands and thought to myself, it sounds all wrong and redundant. `git history` sounds like too broad of a name for one thing. I'd want to have it be `git <verb>` instead. All the operations listed can be done with rebase:
- `git history drop`: Instead, rebase interactively and drop one or more commits.
- `git history reorder`: Interactively rebasing makes this work already.
- `git history split`: Insert a pause in the interactive rebase. Do a soft reset or something to the previous commit, and use `git restore` to unstage the changes (there might be a more efficient way to do this in one step, but idk). Then, do `git add -p` to add what you want, commit, as many times as you want to split the patch. Then continue the rebase.
- `git history reword`: There is a reword option in interactive rebase mode, and also a fixup-like option to do it as well if you want to postpone the rebase.
- `git history squash`: Rebase can do this now in multiple ways.
Rebasing is not that hard. It is the Swiss Army knife of VCS tools. Once you realize that you can just break in the middle of a rebase and do nearly anything (except start another rebase), the world is your oyster. We don't need to spam people with many more single-purpose tools. We need people to understand why the way things are is actually pretty damn good already, if only they read the manual.
Amending a commit behind HEAD is not a simple thing in general. You can have conflicts. When it can be done simply, it can be done in 2 steps with git (or less than 2, since you can amend lots of commits with one rebase). What are the 2 steps? After adding what you want, here they are:
- `git commit --fixup HEAD~3`
- `git rebase --autosquash HEAD~4`
The "less than 2 steps" part comes from fixing up more than one commit having no conflicts before the rebase. It is very common to want to stop or run test scripts at various points in the newly modified history, and rebase can trivially help you automate that (but only if you want).
Rebasing literally just does a sequence of steps in the order you specify. You can make those steps complicated, or not, without learning yet another tool. The complexity that is in the rebase tool is practically all necessary.
After using git for many years, I realize now that a lot of thought went into its design. The way it works, especially with the rebase tool, is superior to having a dozen single-purpose tools.
I don't think this particular thing is against the UNIX philosophy either. All of these operations are intimately related just like the operations that a FTP client might do. I can just imagine someone like you looking at FTP or rsync, and saying "This can be 20 different commands! Why don't they make my particular use case into its own command!" There is a place for that kind of logic, but all of the things jj supporters have proposed to me are way too niche to have their own separate commands. My commit edits are complicated, and `git rebase` makes them super simple.
I didn't know that, but I'm not surprised that this is possible because jj allows you to defer conflict resolution. I for one prefer git's step-by-step rebase model that makes you address conflicts at the time of the rebase. If you aren't ready to address conflicts, you shouldn't be mucking around with the commit history yet. Also, if you turn on rerere, then you can also (usually) avoid fixing the same conflicts multiple times.
I think the issue that wakawaka28 has and I also have is, that I don't think we should have lots of "wizards" for specific high-level operations users want to do. Then we will only end up with hundreds of commands, that all do slightly the same. Also it will train (new) users to complain about adding yet another command to do what they want, instead of letting them learn how to combine the already existing commands.
What we should do instead is provide a bunch of primitives, that as high-level are as possible so to not end up with duplicate commands, which is what git does currently. `git history` as a name is somewhat pointless, since the whole point of git is to produce and modify the history. In that sense `git history` already exists, it is called `git`.
I think the issue newbies have is not that git commands are hard per se, but that they don't think in terms of modifying the graph yet, or that they don't know which primitives are available.
That makes his decisions all the worse and in poor taste IMO, because he ought to know that these use cases are well-covered already. Furthermore, I'm not talking to him. I'm talking to anyone who thinks that his patch was a good idea. I am not going to be dazzled by brand names in this conversation lol.
Lol you could just say you disagree. Is it SO alien to you that users of the most popular VCS in the world might like it the way it is? After arguing with you guys, the only deep fact coming through is that people like you get irrationally optimistic about new tools, after the old favorite doesn't appeal to you for some reason.
Yeah your parent is being combative all over this thread, there’s just no reason for it.
For whatever it’s worth, the general stance of the project and most of the community is “git is good, we like jj more, but you should use whatever you prefer.” Lots of us loved git before jj came along, and there’s a lot of cross pollination between the two projects. I hope your parent takes the feedback and chills out.
I can pick only one? Perhaps automatic rebasing then, i.e. that all descendant commits and bookmarks (branches) are automatically updated when you rewrite a commit, e.g. by amending into it.
I don't think I would want to rewrite all branches based on rewriting one of the ancestors of those branches. This only makes sense for local branches, and I just never have such a set of branches. Most rebases are to get ahead of upstream work, and I can't rewrite that. The rest are to rewrite commits that I made, and I collapse all those commits down periodically anyway. In the rare case I might be able to use this feature, rebasing all the other branches (realistically, probably like 1 or 2) would be easy enough to do manually with the feature described in this post. Rebasing and touching up commits is very easy with git interactive rebase. There are also features to automatically reorder commits with, e. g., `git commit --fixup` and `git rebase --autosquash`.
If you have others in mind then go ahead lol. I was just trying to make it easy.
> I don't think I would want to rewrite all branches based on rewriting one of the ancestors of those branches. This only makes sense for local branches, and I just never have such a set of branches.
Yes, it's only meant for local branches. When I used Git, I had a script for rebasing dependent branches. I remember that a coworker had written a similar script.
I think jj is generally more useful for people like me who often have lots of independent and dependent work in progress. If you mostly just have a one review at a time, there's much less benefit. Perhaps I would say that `jj undo` might be the most useful feature for users with simpler development (yes, I know about the reflog, but see the video I linked to in the other message).
I think this is generally only useful, if these branches don't need any other change for updating the ancestor. When they need than you need to work on the branch anyway and rebase other commits or add new ones on top, so you gain nothing compared to "rebase --onto" for each branch separately.
If you don't have anything to update then that would be somewhat pointless to me. You can also just rebase them, when you start working on the branch again or want to merge them.
--
For me branches also represent features, that should have clear boundaries, so when I work on one feature and that means, I need to rebase another one on top instead of being able to just merge them later, this indicates a spaghetti codebase where the interfaces between features are not properly defined and things depend on internals of other things.
>For me branches also represent features, that should have clear boundaries
I try to do this too but I often end up in situations where I have multiple incomplete (in testing, not merged) features with outstanding patches. Instead of one branch per topic, I end up with one branch for a bunch of related stuff. I then rebase and pause at the feature boundaries to do more testing. Sometimes, if I find myself doing this a lot, I will use the `exec` feature of `git rebase` to automate my testing.
I think rewriting all related branches can cause problems. It would be really weird to do interactively for one thing. The other problem is that you may have unrelated topic branches broken by such a change. If you have a broken patch X that reveals problem Y on branch Z1, but you are working on fixing that on Z2, you may lose your ability to reproduce the Z1 issue if X is fixed on every branch. What if you get conflicts on all those branches? What does this do to the reflog? Yikes! It seems more dangerous than git itself.
These complaints are very niche of course, but the problem of rewriting many branches at once is also very niche. It can cause more problems than it solves.
That sounds real painful and confusing. You could end up with a bunch of conflicts in separate commits, on multiple branches, none of which you actually wanted to modify in the first place.
Someone just told me that if you rewrite a commit that is an ancestor of multiple branches, it will (or can) automatically rewrite all the branches.
What does it even mean to not resolve conflicts? Your branch code and/or history is broken until you come around and fix it? If so, forcing you to fix it immediately is better. Aside from the practical implications of deferring conflict resolution, I just can't think of a reason I would do it in the first place. If a branch can't be rebased with zero work, and I don't have time to do that work, I just don't do the rebase.
Sure, my point is just that if you don’t want history re-written, you just don’t re-write it. If you have multiple descendant branches and for some reason you don’t want the children to move as well, you can move them back. It’s almost the opposite of what the article describes; instead of needing to choose to move children, children move automatically and you move ones you don’t want to back. I’ve never wanted to do this but if it happened it would be easy enough to fix.
It looks like this: when a git rebase would pause the rebase to make your resolve a conflict, jj keeps going. When the rebase finishes, if there are any conflicts, they’re displayed in the log with ?? after their ID. jj won’t let you push a branch with conflicts until the conflict is resolved. You can fix the conflict by either editing that commit directly, or my preferred way, which is to make a new change on top and then squash the resolution back in after you read it over.
So there’s a few thins about why this is useful: because children are also rebased, you can see immediately how much work, if any, you have to do. I have an alias to rebase all open branches on top of trunk, and will often pull trunk, run it, and see if I have any work to do to update them. “Oh, only one of my three branches needs work, I’ll work on the two that are fine first” can happen, and that third can just sit there. Or, as I said before, I could move it back on top of its old base, and the conflict disappears. Or, say I suspected none of them conflict, but all three do, and I don’t want to do it at all right this moment, I’ll just jj undo and they’re all unconflicted and back in place.
(sorry about the formatting here. I guess you'll have to copy & paste it to read it)
What I'm saying is that if I want to fix something in D, I do `jj new D` to create a new commit on top of D. Then I make the fix, run tests, etc., and then I run `jj squash` to amend the changes into D. The descendant commits (E through J) then get automatically rebased and the feature bookmarks/branches get updated.
I didn't follow what you about it other changes needed for updating the ancestor. Can you explain in the context of this example?
So what I am saying is that after you created D', it is true, that you need to run `rebase --onto` in git, while it is automatic in jj. But I think updating feature2 and feature3 is only really necessary to do now, when you want to change something in G-J also. If you don't and it wouldn't cause merge conflicts at all (logical and physical) then you could also just do this when you work on top of H or J the next time, so it wouldn't actually cause any more work in git compared to JJ.
--
The other thing I am saying is that I don't really let features depend on each other, I let them specify the API between them first and then develop them independently. Otherwise it is easy to violate boundaries. So the ideal is that any of G,H and I,J works with D,E,F and vice versa. Of course that is tangential and it doesn't always work that way.
I think you're right about people having trouble with `git reset` but it's really not that hard. There is also the newer `git restore` command which is somewhat easier. There are many individual functions which have an inverse, so the word "undo" might not be a good choice for git. You could make a `git undo` that does the `jj undo` logic with the reflog very easily. I'm not sold on the simplifications yet but to be fair I'm not trying to learn it either lol.
> but splitting is harder than selectively adding after blindly merging all changes.
Is the scenario that you make many changes in the working copy and then run `git add -p` a few times until you're happy with what's staged and then you `git commit`? With jj, you would run `jj split` instead of the first `git add -p` and then `jj squash -i` instead of the subsequent ones. There's no need to do anything instead of `git commit`, assuming you gave the first commit a good description when you ran `jj split`. This scenario seems similarly complex with Git and jj. Did you have a different scenario in mind or do you disagree that the complexity is similar between the tools in this scenario? Maybe I'm missing some part of it, like unstaging some of the changes?
> This scenario seems similarly complex with Git and jj.
It is in number of commands ran, but there's a few annoyances around changes getting into the repo automatically.
There's a lot of git commits coming from jj's constant snapshots. Maybe this is a good thing overall, but it brings some silly issues,
What to do when data that shouldn't leave the dev machine gets to the repo? I'm thinking secrets, large files, generated files.
- Leaking secrets by mistake seems easier.
- Getting large files/directories into the git snapshots might degrade git's performance.
It seems that you need to be diligent with your ignores or get forced to learn more advanced commands right away. I guess there's a more advanced history scrub command around though.
No, it avoids doing that (see the link someone shared above). Git actually also rarely overwrites files. The only case I'm aware of are refs, so I think it could happen that a if you modify a branch on two machines and then sync via Dropbox/rsync, one of those changes could get lost.
Ah, you mean to share the repo you are issuing git commands to directly. Yeah I would expect this to cause problems. Surprising to hear that JJ supports this.
This wasn't what I was talking about, I meant that you should create a bare repo and push to it, not that you work directly in a directory in Dropbox.
One little benefit of the op log is that you can use a single `jj undo` to undo all the rebased branches/bookmarks in one go. If you have rebased many branches with `git rebase --update-refs`, you need to reset each of the branches separately AFAIK.
--update-refs, --no-update-refs
Automatically force-update any branches that point to commits that
are being rebased. Any branches that are checked out in a worktree
are not updated in this way.
If the configuration variable rebase.updateRefs is set, then this
option can be used to override and disable this setting.
Are you saying that that text implies that the you can undo the rebase with a single command or that all the reflogs get updated atomically? Or how is it related to the comment you replied to?
Oops. No the text implies that I can't read and answered to a claim which you didn't state, namely that --update-refs can only update specific refs. (This was given by another comment.)
Yes, this is something, that JJ provides and Git does not.