Contributing through a fork
You fixed a library you don't maintain. It takes changes only as pull requests, and may take weeks to merge yours. Your monorepo needs the fix now. So keep the fix in a fork of the library, and let the monorepo follow the fork until the library has taken it.
The fork has three kinds of branches:
main: a copy of the library'smain. Nobody commits on it.patches: your changes on top ofmain. The monorepo follows this branch, so it has every fix, merged upstream or not.- One branch per pull request: each with one change on top of
main, so the library can review and take each on its own. With a single change,patchescan be the pull request's branch.
Here the monorepo made two changes, x and y. In the fork, each has a
branch of its own, q on fix/parser and e on docs/parser, and
patches has both. A scenario
runs the whole workflow.
Follow the fork
Fork the library on its host. Then add the fork to the splice's
.splice as a second upstream, make
it the default, and follow its patches:
git config --file vendor/a/.splice upstream.fork.url 'https://github.com/<you>/lib.git'
git config --file vendor/a/.splice upstream.fork.default-branch patches
git config --file vendor/a/.splice splice.default-upstream fork
git commit -m "follow the fork's patches" -- vendor/a/.splice
[splice]
id = …
commit = …
default-upstream = fork
[upstream "origin"]
url = https://github.com/<them>/lib.git
[upstream "fork"]
url = https://github.com/<you>/lib.git
default-branch = patches
From then on, git splice push vendor/a sends your changes to the
fork's patches, and git splice pull vendor/a brings that branch in.
Open a pull request per change
Clone the fork, with the library as a second remote:
git clone https://github.com/<you>/lib.git && cd lib
git remote add original https://github.com/<them>/lib.git
Give each change a branch that starts at main, and push it:
git switch -c fix/parser origin/main
git cherry-pick <the change's commit on patches>
git push origin fix/parser
Open the pull request against the library. One against the fork's own
main runs the library's CI on it first.
When the library moves on
In the clone of the fork, bring the fork's main up to date. It has
no commits of its own, so that's a fast-forward:
git fetch original
git push origin original/main:main
git fetch origin
Then rebase patches onto it, and force-push it. The same goes for each
pull request branch:
git switch -C patches origin/patches
git rebase origin/main
git push --force-with-lease origin patches
Then pull in the monorepo, as always. When the library has taken one of
your pull requests, the rebase drops that change from patches, since
main has it now. The monorepo already has the change, so its pull
only records the new patches. Here the library took fix/parser as
f. patches is now just g, the documentation change, on top of f,
and the monorepo's r records it:
If the maintainer changed your fix on the way, the rebase conflicts in the fork. Keep the library's version, and the monorepo pulls that.
Push before the fork rebases
Push your changes from the monorepo to patches before anyone rebases
it. Then the rebased patches has them, and a pull only brings in the
fork's version of what the monorepo already has
(patch-branch-rebased).
A change you didn't push in time needs
pull --rebase.
To see where patches was before the rebase, the pull reads the
fetched branch's reflog, like git pull --rebase. That reflog exists
only on your machine. A colleague who fetches patches for the first
time after the rebase has none, and gets a conflict where your pull went
through
(a clone that fetched after the rebase).
In practice
This repository
dogfoods the
workflow with scrut, its
test runner:
scrut/ is a splice that follows a fork,
roschaefer/scrut. The fork's
pull requests
show each change on its way back to Meta.