Parallelized commits
Whenever I work on a non-trivial task, often I get pulled into a side quest, like making a refactor to tidy things up first before diving into a meaty commit.
@ C
│ task: add something big
○ B
│ sidequest: refactor
○ A
│ task: add something small
◆ main
If this side quest is a blemish on the eventual pull request, I like to reorder the commits, and spin it off as a separate pull request first. Here's how I'd do it in jj:
jj rebase -r B --before A
@ C
│ task: add something big
○ A
│ task: add something small
○ B
│ sidequest: refactor
◆ main
Then I'd do a jj bookmark create -r B sidequest && jj git push and create a pull request for merging B into main. This works great when there's only one side quest.
What if there's more?
Yesterday, I worked on something major that involved three side quests. The commits went something like this:
@ F
│ task: add feature that uses SubTypeA/B
○ E
│ sidequest: add SubTypeA/B, refactor
○ D
│ task: add feature that uses NewFunction
○ C
│ sidequest: DRY stuff into NewFunction
○ B
│ task: add something to the db schema
○ A
│ sidequest: reorder fields in the db schema
◆ main
For brevity, let's drop the detailed messages and rename them - sqX for side quests, and tX for tasks:
@ t3
│
○ sq3
│
○ t2
│
○ sq2
│
○ t1
│
○ sq1
│
◆ main
That's 3 side quests that don't depend on each other and are rather well isolated, so I wanted to send them off as 3 separate pull requests first.
First I reordered them:
@ t3
│
○ t2
│
○ t1
│
○ sq3
│
○ sq2
│
○ sq1
│
◆ main
But how do I create 3 separate pull requests (sq1 -> main, sq2 -> main, sq3 -> main)? My first instinct was to spinoff the sqX commits like this:
...
│
○ t1
│
│ ○ sq3
├─╯
│ ○ sq2
├─╯
│ ○ sq1
├─╯
○ main
.. but this does not work, because the tX commits depend on the sqX commits. We need the sqX commits to be parents of t1, like this:
...
│
○ t1
│
├─┬─╮
│ │ ○ sq3
│ │ │
│ ○ │ sq2
│ ├─╯
○ │ sq1
├─╯
◆ main
So how do we get there?
Okay let's restart from right after we rearranged the sqX commits:
...
│
○ t1
│
○ sq3
│
○ sq2
│
○ sq1
│
◆ main
We can first rebase t1 and specify sq1, sq2, sq3 as the parents, like this:
jj rebase -s t1 -d sq1 -d sq2 -d sq3
But this results in this shape:
...
│
○ t1
├─┬─╮
○ │ │ sq3
├─╯ │
○ │ sq2
├───╯
○ sq1
│
◆ main
Not quite what we want.
sq1issq2's parent. If we create a pull request for a branch atsq2targeting main, it will contain bothsq1andsq2.sq2issq3's parent. If we create a pull request for a branch atsq3targeting main, it will containsq1,sq2, andsq3.
We want 3 pull requests, 1 commit each. So we need to do 2 more rebases, so that sq2 and sq3 have main as their parent. Start with sq2:
jj rebase -s sq2 -d main
...
│
○ t1
├─┬─╮
│ ○ │ sq3
│ ├─╯
│ ○ sq2
│ │
○ │ sq1
├─╯
◆ main
.. then sq3:
jj rebase -s sq3 -d main
...
│
○ t1
│
├─┬─╮
│ │ ○ sq3
│ │ │
│ ○ │ sq2
│ ├─╯
○ │ sq1
├─╯
◆ main
We got it! But that's after 3 commands:
jj rebase -s t1 -d sq1 -d sq2 -d sq3
jj rebase -s sq2 -d main
jj rebase -s sq3 -d main
.. and it's very confusing, for me at least. Surely there's a better way to do it. I turned to my colleague Glen and described the situation. Without thinking, he said,
Simple. Just "jj parallelize" the first commit colon-colon the third one.
I didn't quite believe him, but jj has its magical moments. So I reset the state of the repo:
...
│
○ t1
│
○ sq3
│
○ sq2
│
○ sq1
│
◆ main
.. and typed:
jj parallelize sq1::sq3
...
│
○ t1
│
├─┬─╮
│ │ ○ sq3
│ │ │
│ ○ │ sq2
│ ├─╯
○ │ sq1
├─╯
◆ main
Holy crap.
Here's the link to jj: https://github.com/jj-vcs/jj