Focus on judiciously: mostly (functionally) atomic commits that are not every tiny change, not largely meaningless time-based snapshots, not Gerrit-style single commits for entire features, etc. I’m well aware of the mental leap you’re talking about, it’s neither hard to understand nor the liberation you might think it is. To achieve what we want to achieve we either need to be able to split, or “commit” with such atomicity that more time is wasted come squash time.* If you don’t get that, totally fine, but then you just don’t belong very much to this very conversation.
* Or we can completely change how we write code…
With jj, your working copy is always a commit. This encourages a "commit often and messy, clean up later" approach. You can rapidly iterate with or without AI, letting jj automatically save every change without stopping to craft perfect atomic commits.
Later, you use a simple jj squash to combine all those small, iterative changes into logical, clean commits before you share it. The atomicity is created retroactively, not upfront. For a finely-tuned magit workflow this might feel wrong, but for rapid, exploratory and AI-driven iteration, it's a more natural fit.
Small commits are the same small commits in jj as in git, you just split instead of add -p.
You can also stage or unstage lines by selecting them first and press the relevan keys.
If you press ‘return’ on a hunk, it brings you directly the the line to edit.
actually, with jj's seamless rebases, it'd work exactly as you'd want it to (but maybe not how you'd expect if you're used to the git way) when aimed at a commit in the middle of a branch: pick lines which stay below, above or in the middle commit, which you can edit transparently.
Interactive rebasing is a breeze. Editing commits is very fast (reordering, dropping rewording, splitting,…). Same with dealing with merge conflicts (Emacs have various merge packages builtin).
The same speed Vim brings to editing, that’s nearly the same speed you get with magit for git.
Does not sound any easier or more intuitive than git.
It isn’t how git porcelain wants you to work, but it does make sense once you stop thinking in git. Working copy being a first class commit unlocks all the dag manipulation tools without stashing, having to resolve conflicts in middle of tricky operations, etc. ‘Easier’ is exactly the point, but it’s about workflows which are very inconvenient in git like rebasing stacked PRs.
I’ve never used magit but I used GitUp (https://gitup.co/) for years before jj. I don’t find the jj one super natural, but I feel like that’s mostly a matter of keyboard shortcuts — I need to see if they can be customized.
I end up with "neatly separated does-one-thing commits" but I get there by building them up as I go along, not by writing a ton of code and then trying to split it up afterwards.
An incomplete list of wins vs StGit includes:
- jj makes managing multiple branches fluid, whereas stg has limited tools for managing patches between stacks. 'stg pick' is largely all there is. It's a real dance to move a patch between stacks.
- jj has a much better system for naming changes. I'm super jealous of how jj solved this problem. StGit requires you to name the patches. I added the feature that allows StGit to refer to patches by relative or absolute index in addition to by name. jj's immutable change ids that can be referenced by unambiguous prefix is the correct answer to this problem.
- 'jj rebase' is so vastly superior to stg push/pop/sink/float for reordering changes that I don't even know where to start. It wasn't immediately obvious to me just how flexible, simple, and powerful 'jj rebase' is when I first started using jj, I have learned that it is in its own league relative to StGit's stack ordering story.
- Similarly 'jj squash' makes 'stg squash' look amateurish.
I could go on. If you're a StGit user, you owe it to yourself to give jj a proper try.
* I have a branch foo with a stack of patches which are the thing I'm working on, based on a master branch
* I have a separate stack of patches on a branch bar (let's say this is a feature that interacts with foo but it's not ready to upstream yet or maybe at all)
* I want to test and work on the combination of foo and bar and make changes that might need to be updates to some patch in foo, or to some patch in bar
At the moment I pick all the patches in foo onto bar in order to do the work and updates in this combined branch, squashing fixes and changes into appropriate patches. Then once I'm happy I go back to the foo branch, blow away the old patches and re-pick them off my combined branch.
This works but feels super clunky -- does jj do better here? That would be a real incentive to try it out.
For the rest, they don't sound like they're strong enough to beat "I've used stgit for 10 years and have a solid workflow with it".
And I just scanned the jj rebase docs and it looks awful, everything I moved to stgit to get away from. I do not want to think about managing a patch stack in terms of "move a bunch of revisions to a different parent". I like having a straightforward stack of patches that I can push, pop and reorder and list. Don't make me think about graphs of commits and multiple rebase suboptions and all that for something that I do all the time in my main workflow, please.
Combined with jj absorb, some people just work this way all the time, even.
> I like having a straightforward stack of patches that I can push, pop and reorder and list.
You can work this way too, what you'd want is `jj rebase` with -A, -B, and -r: r for the revision, and A and B for the before and after you want to move it too. This lets you reorder things however you want pretty straightforwardly. I tend to work in a stack of patches too, or at least, way more than I used to.
Anyway you should use the tools you like, it’s all good.
For example, I frequently use `jj up` to rebase the current branch on main. Likewise, `jj pop` rebases just the current commit (popping it from its current place). I even have a `jj ppop` - better name suggestions are welcome - which does this but for the parent commit.
I suspect that the once-off effort to write your own commands would take no longer than it would take to read the documentation if the commands already existed, but with the hopeful extra benefit of giving you a better understanding of how to use rebase for those once in six months weird requirements when they may arise.
But to be clear, I'm not suggesting you must or even should put in this effort if you have something that works for you. My reply is mostly so that anyone who comes across this discussion and sees Steve's mention of -A, -B, etc isn't scared off by them. Whilst they're always there for you, you can use the power it gives you but in the form of single function commands that don't require you to think.
---
For anyone wondering, the aliases I mentioned. These can be dropped in your jj config with `jj config edit --user`.
[aliases]
up = ["rebase", "--skip-emptied", "-d", "trunk()"]`
pop = ["rebase", "-r", "@", "-d", "trunk()"]
ppop = ["rebase", "-r", "@-", "-d", "trunk()"]Exactly, me too. Things like `absorb` I'll take, but I don't want jj's opinionated approach to version control. And not only do I not want it for _me_, but I also don't want it for newbies because hiding too much of the underlying design, design issues, etc., seems counterproductive to me.
It actually sounds like you're confusing jj with something else.