Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/PATCHSTACK.md
Original file line number Diff line number Diff line change
@@ -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")))
```
26 changes: 26 additions & 0 deletions .github/pull.yml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 4 additions & 2 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
63 changes: 63 additions & 0 deletions .github/workflows/patchstack-sync.yml
Original file line number Diff line number Diff line change
@@ -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-*"