Hacker News new | ask | show | jobs
by qudat 238 days ago
When you start doing git surgery where there are commit chains that need to stay logical is where JJ starts to shine. If you are constantly editing previous commits and placing code in your working area into those previous commits and rebasing original/main.

I also really like that every change is automatically committed. It’s a great mental model once you get used to it.

1 comments

Git rebase works fairly well and is somewhat uneventful, unless there are major changes happening. I do hate the experience when one file was remove in my feature branch, but main did a major refactor which affected the original file, so conflicts are a bit awkward then - but other than that, this seems like a fairly clean workflow.
Git rebase is an enormous pain in the ass.

Rebases must be done linearly. And right now! Oops, you made an error in an earlier stage of the rebase? Start over, good luck! Want to check something from earlier while you’re in the middle? Sorry, you’re in a modal state and you don’t get to use your regular git tooling.

You can just record all your changes with git commit --fixup and then do a non-interactive rebase that just applies all the changes.

You can use all the regular git tools in a rebase, in fact it would be quite useless without. You can also just jump to other branches or record a fix to a previous commit. It doesn't matter what you do in the meantime, it only cares what is the HEAD, when you call git rebase --continue, and then it only performs what commands you specify in the rebase todo. You can even change the todo list at any time.

Yes, it's certainly possible to do all those things with Git. Compared to jj, it's just much harder to do, easier to mess up, and harder to recover from if you do mess up.
I just gave you an example how it is not "much harder".
As I understood the scenario:

1. We're rewriting some commits. Let's say a chain of commits A through G. We want to make some change to commits A and D.

2. As we're editing commit D, we realized that we need to make some changes to B to match the updated A.

3. Also while editing D we realized that we want to take a look at the state in A to see how something worked there.

With jj, here's what I would do:

1. Run `jj new A`, make the changes, then `jj squash` to squash the changes in to A and propagate them through the chain.

2. Run `jj new D` to make changes. We now notice that we wanted some changes to go into B. We can make the changes in the working copy and run `jj squash --into B -i` to interactively select the changes to squash into B.

3. Run `jj new A` to create a new working-copy commit on top of A, look around in your IDE or whatever, then run `jj edit <old commit on top of D>`. Then run `jj squash` to squash the remaining changes in the working copy into D.

I think you know the steps to do with Git so I won't bother writing them down. I find them less intuitive anyway.

I don’t know about you, but I am tired of having to remember the dozens of simple, one-off workarounds to every single thing I want to actually accomplish.

A few months back I had to sanitize the commit history of a repo that needed certain files removed from the entire history. I could have read the manpage for `git filter-branch`, but in jj editing commits is just a normal, sane part of your workflow. It was a blindingly obvious one-liner that just used all the commands I use every day already.

Even better, it was fast. Because “edit the contents of a bunch of commits” is a first-class operation in jj, there’s no need to repeatedly check out and re-commit. Everything can be done directly against the backing repository.

git has rerere for this usecase, jj doesn't - you have to find the conflict resolution manually in your history in this case if you made a mistake.
git has rere, but jj doesn't because its equivalent is built in. https://github.com/jj-vcs/jj/issues/175#issuecomment-1079831... is some discussion about the differences here.