Add spork strategy: tooling and documentation

Spork-create mise task sets up a floating-branch soft-fork of a
mirrored upstream project with daily mirror-sync via Forgejo Actions.
Includes explanation card, how-to guides for setup and branch
management, and the spork-create uv script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-03-28 22:58:10 -07:00
commit 6ecfaf02b6
6 changed files with 675 additions and 0 deletions

View file

@ -0,0 +1,84 @@
---
title: Create a Spork
modified: 2026-03-28
last-reviewed: 2026-03-28
tags:
- how-to
- git
- forgejo
---
# Create a Spork
How to set up a floating-branch soft-fork ("spork") of a mirrored upstream project using `mise run spork-create`.
## Prerequisites
- Mirror already exists at `mirrors/<project>` on forge (see [[manage-forgejo-mirrors]])
- 1Password CLI authenticated (`op` CLI)
- SSH access to `forge.ops.eblu.me:2222`
## Create the spork
```fish
mise run spork-create kingfisher
```
This will:
1. Fork `mirrors/kingfisher``eblume/kingfisher` on forge
2. Create a `blumeops` branch from upstream's main branch
3. Remove any upstream `.forgejo/` directory (if present)
4. Add `.forgejo/workflows/mirror-sync.yaml` and commit it
5. Set `blumeops` as the default branch
6. Clone to `~/code/3rd/kingfisher` with three remotes: `origin`, `mirror`, `upstream`
Options:
```fish
mise run spork-create kingfisher --dry-run # preview only
mise run spork-create kingfisher --no-clone # skip local clone
mise run spork-create kingfisher --main-branch dev # override branch name
```
## Verify the setup
```fish
cd ~/code/3rd/kingfisher
git remote -v
# origin ssh://forgejo@forge.ops.eblu.me:2222/eblume/kingfisher.git (fetch)
# mirror ssh://forgejo@forge.ops.eblu.me:2222/mirrors/kingfisher.git (fetch)
# upstream https://github.com/mongodb/kingfisher.git (fetch)
git branch -a
# * blumeops
# remotes/origin/blumeops
# remotes/origin/main
```
## What happens next
The mirror-sync workflow runs daily at 05:00 UTC and:
- Fast-forwards `main` from the mirror
- Rebases `blumeops` on top of `main`
- Rebases any `feature/local/*` and `feature/upstream/*` branches
- Rebuilds the `deploy` branch (all features merged)
See [[manage-spork-branches]] for working with feature branches.
## Terminology
| Term | Meaning |
|------|---------|
| `origin` | Your mutable fork at `eblume/<project>` on forge |
| `mirror` | Read-only upstream mirror at `mirrors/<project>` on forge |
| `upstream` | Canonical upstream repository (e.g., GitHub) |
| `main` | Clean upstream tracking branch (may be named `master`, `dev`, etc.) |
| `blumeops` | Default branch — upstream + local workflows/tooling |
| `deploy` | Build artifact branch — everything merged, used for deployments |
## See also
- [[manage-spork-branches]] — creating feature branches, upstreamable vs local
- [[manage-forgejo-mirrors]] — mirror setup and PAT rotation

View file

@ -145,3 +145,5 @@ Trigger a manual sync on one mirror to confirm the new PAT works:
- [[forgejo]] — Forgejo service reference
- [[gandi-operations]] — Similar PAT rotation workflow for Gandi DNS
- [[spork-strategy]] — floating-branch soft-fork strategy explanation
- [[create-a-spork]] — create a spork on top of a mirror

View file

@ -0,0 +1,116 @@
---
title: Manage Spork Branches
modified: 2026-03-28
last-reviewed: 2026-03-28
tags:
- how-to
- git
- forgejo
---
# Manage Spork Branches
How to create, maintain, and reason about feature branches on a sporked repository. See [[create-a-spork]] for initial setup.
## Branch types
### Upstreamable features (`feature/upstream/*`)
Changes intended to be contributed upstream. Branch off `main` so the diff is clean — no local tooling or workflows mixed in.
```fish
cd ~/code/3rd/kingfisher
git fetch origin
git checkout -b feature/upstream/forgejo-support origin/main
# Make changes, commit as normal
git push -u origin feature/upstream/forgejo-support
```
The mirror-sync workflow will automatically rebase this branch onto `main` each day.
To see what the upstream contribution looks like:
```fish
git log main..feature/upstream/forgejo-support --oneline
git diff main...feature/upstream/forgejo-support
```
To create a preview PR on forge (targets the mirror, not upstream):
```fish
# From the eblume/<project> repo, PR targeting mirrors/<project>:main
# This gives a public URL showing the diff without filing upstream
tea pr create --repo mirrors/kingfisher --head eblume/kingfisher:feature/upstream/forgejo-support --base main
```
When ready to contribute upstream, manually translate the branch to a GitHub PR.
### Non-upstreamable features (`feature/local/*`)
Local-only changes that will never go upstream. Branch off `blumeops` so you have access to all local tooling.
```fish
cd ~/code/3rd/kingfisher
git fetch origin
git checkout -b feature/local/custom-rules origin/blumeops
# Make changes, commit as normal
git push -u origin feature/local/custom-rules
```
The mirror-sync workflow will automatically rebase this branch onto `blumeops` each day.
## The `deploy` branch
The `deploy` branch is a build artifact — rebuilt fresh by mirror-sync daily. It contains everything merged together: `blumeops` + all `feature/local/*` + all `feature/upstream/*`. Use this branch for deployments (e.g., ArgoCD `targetRevision`).
**Never commit to or work from `deploy`.**
## Working with rebasing branches
Because mirror-sync force-pushes rebased branches daily, local checkouts will diverge. Always pull with rebase:
```fish
git pull --rebase origin feature/upstream/my-change
```
Or set it as default for the repo:
```fish
git config pull.rebase true
```
This is the fundamental trade-off of the spork strategy: small frequent rebases instead of rare catastrophic merges.
## When rebases fail
If upstream changes conflict with a feature branch, mirror-sync will skip that branch and log an error. Recovery:
```fish
cd ~/code/3rd/kingfisher
git fetch origin
git checkout feature/upstream/my-change
git rebase origin/main
# Resolve conflicts...
git push --force-with-lease origin feature/upstream/my-change
```
The next mirror-sync run will pick up the resolved branch and rebuild `deploy`.
**TODO:** Rebase failures are currently only visible in the Forgejo Actions UI. Alerting via Grafana is planned but not yet implemented.
## Future: `.spork.toml`
For repos with multiple feature branches, a `.spork.toml` file on the `blumeops` branch could declare:
- **Branch dependencies** (stacked branches — `bar` depends on `foo`)
- **Feature descriptions** (what the branch is for, in prose)
- **Upstream/local classification** (as an alternative to the naming convention)
This is not yet implemented. For now, the `feature/upstream/*` vs `feature/local/*` naming convention is the source of truth.
## See also
- [[create-a-spork]] — initial setup
- [[manage-forgejo-mirrors]] — mirror setup and PAT rotation