blog.chay.dev

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.

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

#dev #git #jj