Git: Multirepo patch set

In the context of Alejandro Colomar stepping down as a man-pages maintainer :(, I learned from him that it is possible with git to create an email patch set that spans multiple target repositories. Specifically, he pointed to a review thread by Jiri Olsa where this was done, and whose outline takes a similar shape to this (simplified):

Other than in normal review threads, every mail subject is prefixed with an additional tag here, to indicate which project it belongs to.

It was not obvious to me how to best achieve it with the existing tools, so I attempted to reconstruct how he had done it, and ended up with the workflow that I will describe below. This is not the same workflow that Jiri Olsa has been using after all, but it’s close, and unusual enough that it’s maybe worth documenting here.

1. Pull both original repositories in the same local repository

We first make sure that all necessary commits are stored in the same local repository, by pulling in a second remote repository with git fetch.

This is unusual, but it is possible. As a result, your local repository contains two unrelated commit histories, for instance one for the linux kernel and one for the man pages:

linux-base linux-feature man-base man-feature linux origin man-pages origin linux worktree man-pages worktree local repository

You can simplify your life here a bit by using git worktrees – with these, your can keep doing your Linux work in a separate directory to your man page work, but still keep all of the changes in the same repository.

In the example above, we have prepared two commits in the linux-feature branch, based on the linux-base branch from upstream, as well as one commit in the man-feature branch, based on man-base.

Example

Starting from two repositories /tmp/git/linux-origin and /tmp/git/man-origin, let’s fetch both repositories into the same local repository and create a worktree for each:

gnoack:/tmp/git$ git clone /tmp/git/linux-origin linux
Cloning into 'linux'...
done.
gnoack:/tmp/git$ cd linux
gnoack:/tmp/git/linux(main)$ git tag linux-base
gnoack:/tmp/git/linux(main)$ git remote add man-origin /tmp/git/man-origin
gnoack:/tmp/git/linux(main)$ git fetch man-origin
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 12 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (12/12), 948 bytes | 948.00 KiB/s, done.
From /tmp/git/man-origin
 * [new branch]      main       -> man-origin/main
gnoack:/tmp/git/linux(main)$ git worktree add /tmp/git/man man-origin/main
Preparing worktree (detached HEAD efbfa45)
HEAD is now at efbfa45 4

We now have a worktree for the man pages at /tmp/git/man and one for Linux at /tmp/git/linux.

We now prepare the Linux and man page commits on top of these git repositories in the usual way, and make sure that we create branches for the pristine states called man-base and linux-base, and call our work branches man-feature and linux-feature.

2. Format a patch set that spans two otherwise unrelated branches

Create a giant patch set that spans two branches. Indicating the desired commit range by using dotted range notation twice.

This invocation is compatible with common flags like -v3 for the patch set version and --cover-letter.

We use --subject-prefix so that the mail subject is additionally qualified with an abbreviation for the main project.

git format-patch -v3 --cover-letter \
   --subject-prefix="PATCH proj"    \
   linux-base..linux-feature        \
   man-base..man-feature

Remember from the gitrevisions(7) man page that a revision range in Git parlance is a set of commits, which are not necessarily ordered in a single linear chain. (Somewhat surprisingly, they are also not all connected here, as you would think from reading the man page.)

git format-patch includes some additional features for controlling the ordering, but in practice it seems that commits are ordered by date if they are not connected.

3. Final hand-editing

Hand-edit the subject prefix in the man page commit’s email, so that the man page commit stands out among the others for the reviewers.

And sure enough, after git send-email, the mail thread has the expected form (in mutt):

 [PATCH proj v3 0/3] foobar: Add transmogrifier
 ├─>[PATCH proj v3 1/3] foobar: Prepare flux compensator
 ├─>[PATCH proj v3 2/3] foobar: Add transmogrifier feature
 └─>[PATCH man v3 3/3] foobar.1: Document transmogrification

Comments