diff --git a/.github/PATCHSTACK.md b/.github/PATCHSTACK.md new file mode 100644 index 00000000..f8f50aee --- /dev/null +++ b/.github/PATCHSTACK.md @@ -0,0 +1,77 @@ +# The patch stack + +This is a fork of [r-lib/cpp11](https://github.com/r-lib/cpp11) that carries a +handful of changes upstream does not ship. +It is maintained as a *patch stack*: +one branch per change, +each a single commit on top of upstream, +and one branch that is upstream plus all of them. + +## The branches + +- **`main`** is a 1:1 mirror of `r-lib/cpp11`'s `main`. + The [Pull app](https://pull.git.ci/) hard-resets it on every sync + ([`pull.yml`](pull.yml)), + so a commit made here is discarded the next time upstream moves. + Never commit to it. +- **`fork`** is the default branch and the one to install: + `main` plus every patch, squashed in lexicographic branch order. + It is rebuilt from scratch on each sync — never commit to it either, + and never merge into it. +- **`a-fork-infra`**, **`b-*`**, **`f-*`** are the patch branches, + each based on `main`. + `a-` is this fork's own infrastructure (it sorts first, so it lands first), + `b-` is a bug fix, `f-` is a feature. + [`patchstack-sync.yml`](workflows/patchstack-sync.yml) picks up + `b-*` and `f-*` by glob, + so a working branch under any other name is left alone. + +## The sync + +[`patchstack-sync.yml`](workflows/patchstack-sync.yml) runs +[krlmlr/patchstack](https://github.com/krlmlr/patchstack) nightly, +and on demand from the Actions tab. +Each run replays every patch branch onto the current `main`, +squashes them into a fresh `fork` in branch order, +and pushes the lot atomically. + +A patch that no longer applies is left at its old commit +and dropped from that run's `fork`, +so one broken patch never blocks the others — +resolve it by rebasing that branch onto `main` by hand and pushing it. +A patch whose change has landed upstream replays to nothing; +its branch is deleted and the disposition recorded in `refs/notes/patchstack`. + +## Adding a patch + +```bash +git fetch origin +git switch -c f-my-change origin/main +# ... one commit ... +git push -u origin f-my-change +``` + +The next sync folds it into `fork`. +Keep it to a single commit where you can: +the stack is easier to read, +and the squash into `fork` is what everyone consumes anyway. + +Each patch is a candidate for upstream. +Opening a pull request from its branch against `r-lib/cpp11` +costs nothing here — the branch stays exactly where the sync expects it — +and a merged patch cleans itself up on the following run. + +## Installing + +```r +pak::pak("krlmlr/cpp11") +``` + +`fork` is the default branch, so this installs the whole stack. +It is also built by +[krlmlr.r-universe.dev](https://krlmlr.r-universe.dev), +which is the faster route: + +```r +install.packages("cpp11", repos = c("https://krlmlr.r-universe.dev", getOption("repos"))) +``` diff --git a/.github/pull.yml b/.github/pull.yml new file mode 100644 index 00000000..1162ef0a --- /dev/null +++ b/.github/pull.yml @@ -0,0 +1,26 @@ +# Configuration for the "Pull" GitHub App, which keeps this fork's `main` in sync +# with the upstream r-lib/cpp11 repository. +# +# Pointers: +# - App / install / manage: https://pull.git.ci/ +# - Source: https://github.com/wei/pull +# - Configuration reference: https://github.com/wei/pull/blob/master/docs/CONFIGURATIONS.md +# - Trigger a manual sync: https://pull.git.ci/process/krlmlr/cpp11 +# +# Notes: +# - This file must live on the fork's default branch (`fork`) for Pull to read it. +# - `main` is a 1:1 mirror of r-lib/cpp11's `main`, so `hardreset` is the only +# correct merge method: any commit made on it here is discarded on the next sync. +# Nothing may be committed to `main` -- the patch branches carry this fork's work. +# - `fork` has no rule on purpose: it is not a mirror but `main` plus the squashed +# patch stack, and .github/workflows/patchstack-sync.yml rebuilds it once Pull +# has moved `main` forward. +# - Deleting this file would not disable Pull: without a configuration it hard-resets +# the fork's default branch from upstream, which is exactly what `fork` must not do. + +version: "1" + +rules: + - base: main + upstream: r-lib:main + mergeMethod: hardreset diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index a4aafd29..22703f58 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -6,9 +6,11 @@ # usethis::use_github_action("check-standard") will install it. on: push: - branches: [main, master] + # `fork` is this fork's integration branch, rebuilt from `main` plus the + # patch stack; checking it is what says the stack still builds. + branches: [main, master, fork] pull_request: - branches: [main, master] + branches: [main, master, fork] name: R-CMD-check diff --git a/.github/workflows/patchstack-sync.yml b/.github/workflows/patchstack-sync.yml new file mode 100644 index 00000000..ecfb6829 --- /dev/null +++ b/.github/workflows/patchstack-sync.yml @@ -0,0 +1,63 @@ +name: Patchstack sync + +# Rebuilds `fork` -- this repository's default branch -- as `main` plus the +# squashed patch stack, replaying every patch branch onto `main` first. +# +# `main` is a 1:1 mirror of r-lib/cpp11, hard-reset by the Pull app +# (see .github/pull.yml), so it is the upstream this sync tracks and there is no +# second remote to fetch. The patch branches are the `b-*` (bug fix) and `f-*` +# (feature) branches; everything else in this repository is left alone. +# +# The push needs a token that may write workflow files, because `fork` carries +# this file and .github/pull.yml through the `a-fork-infra` patch. GITHUB_TOKEN +# may not, and it only matters when their content changes -- a sync that leaves +# them untouched goes through with the default token. Set PATCHSTACK_TOKEN to a +# fine-grained PAT with Contents: read and write and Workflows: read and write +# to make the exception unnecessary. + +on: + schedule: + # Off the hour: runs scheduled at :00 queue behind everyone else's. + - cron: "23 4 * * *" + workflow_dispatch: + repository_dispatch: + types: [main-updated] + +concurrency: + group: patchstack-sync + cancel-in-progress: false + +permissions: + contents: read + +jobs: + sync: + name: Rebuild the fork branch + runs-on: ubuntu-latest + timeout-minutes: 20 + + permissions: + contents: write + + steps: + - name: Check out this repository + uses: actions/checkout@v6 + with: + # The replay needs every patch branch's history; blobs come on demand. + fetch-depth: 0 + filter: blob:none + token: ${{ secrets.PATCHSTACK_TOKEN || github.token }} + + - name: Check out patchstack + uses: actions/checkout@v6 + with: + repository: krlmlr/patchstack + path: .patchstack + persist-credentials: false + + - name: Sync + uses: ./.patchstack + with: + main_branch: fork + upstream_branch: main + patch_glob: "b-* f-*"