Hacker News new | ask | show | jobs
by martinvonz 228 days ago
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.

1 comments

1. Edit changes, run `git commit --fixup=A`.

2. Edit changes for D. Also edit changes for B. Interactively `git add` changes for B. `git commit --fixup=B`.

3. Finish changes for D, then call git commit --fixup=D

Before push I run `git rebase --autosquash`, optionally `git rebase --exec='make check'` to rerun all the tests on the changed commits.

Where is that "much harder"?

Ah, I see, so you avoid interactive rebase and instead make all changes in the working copy and use `git commit --fixup` and `git rebase --autosquash` . Makes sense, but doesn't it break down when there are conflicts between the changes you're making in the working copy and the target commit? How do you adjust the steps if there were conflicts between the changes we wanted to make to A and the changes already present in B?
> Ah, I see, so you avoid interactive rebase and instead make all changes in the working copy and use `git commit --fixup` and `git rebase -i .

I wouldn't say I avoid this, I also run `git rebase -i` several times per day, and I also often use `git commit --fixup` during a rebase.

> Makes sense, but doesn't it break down when there are conflicts between the changes you're making in the working copy and the target commit?

Yes, but wouldn't this be the same in JJ, when you do your changes on top of A, and later squash them into D? If you don't want to have the changes, you can also checkout D and do the changes there. Then you have two options:

- `git commit --fixup`, later do `git rebase`

- `git commit --amend`, and `git rebase --onto`

Most times I do the thing described earlier and just solve the conflicts, because that's just a single command. Also when its only a single case, I use `git stash`. (The workflow then is do random fix, git stash, then figure out where these should go, git rebase)

> How do you adjust the steps if there were conflicts between the changes we wanted to make to A and the changes already present in B?

I just resolve them? I think I don't understand this question.

> I just resolve them? I think I don't understand this question.

In order to make changes to commit A when there are conflicting changes in B, I was thinking that you would have to use interactive rebase instead because you can no longer make those changes in the working copy and use `git commit --fixup`, right? And because there will now be conflicts in commit B, you will be in this "interrupted rebase" state where you have conflicts in the staging area and it's a bit tricky (IMO) to leave those and look around somewhere else and then come back and resolve the conflicts and continue the rebase later.

> Yes, but wouldn't this be the same in JJ, when you do your changes on top of A, and later squash them into D?

The difference is that we don't end up in an interrupted rebase. If we squashed some changes into A and that resulted in conflicts in B, then we would then create a new working-copy commit on top of the conflicted B (I call all of the related commits B even if they've been rewritten - I hope that's not too confusing). We then resolve the conflicts and squash the resolution into B and the resolution gets propagated to the descendants. We are free at any time to check out any other commit etc.; there's no interrupted rebase or unfinished conflicts we need to take care of first. I hope that clarifies.

> I was thinking that you would have to use interactive rebase instead because you can no longer make those changes in the working copy and use `git commit --fixup`, right? And because there will now be conflicts in commit B, you will be in this "interrupted rebase" state

Yes.

I don't see the drawback honestly. Invoking git rebase, means I want to resolve conflicts now, when I want to do that later, I can just call git rebase later. When you want to work on top of the B with conflicts, the code wouldn't even compile, so I expect JJ, to just give you the code before the squash, right? How is this different from in Git?

The difference is that jj doesn't force you to resolve the conflict right away. I agree that you usually want to do that anyway, but it has happened to me many times that some conflict turned out to be more complicated than I had time for at the moment and I needed to work on something else for a while. When using Git, I would typically abort the rebase in such cases, which is not so bad if you have rerere enabled (I can't remember if it records any resolutions I had staged or if that's only one you commit).

Anyway, I'm just explaining how jj works and what I prefer. As Steve always says, you should use the tools you prefer :)