docs: add upstream-PR workflow + lnbits branch-model reference
Two new docs under docs/: - upstream-prs.md — how to send a PR upstream using the ~/dev/upstream-prs/ worktree flow. Opens with a "Primer" section for anyone new to forks / pull requests (the three remote roles — upstream / github-fork / origin — explained as a table), then the per-project prerequisites, the prb/prc helper-driven workflow, and a "Common pitfalls" section. - lnbits-upstream-flow.md — reference for how lnbits/lnbits actually moves: the dev/main branch split, the squash-merge PR convention, the non-FF release merge from dev into main, and how to read the history with --first-parent. Adapted from internal aiolabs notes with the fork-versioning specifics stripped; closing section is generic guidance for anyone maintaining their own long-lived fork. README links both from a new "Further reading" section. The prior "Contributing" section is retitled "Contributing to this scaffold" to avoid colliding with the new upstream-PR doc on the term. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3f88551f1b
commit
30fbd0cef9
3 changed files with 278 additions and 1 deletions
13
README.md
13
README.md
|
|
@ -192,7 +192,18 @@ lb dev # cd ~/dev/lnbits/dev
|
|||
lb fix-issue-123 # cd ~/dev/lnbits/fix-issue-123
|
||||
```
|
||||
|
||||
## Contributing
|
||||
## Further reading
|
||||
|
||||
- [`docs/remotes.md`](docs/remotes.md) — the three remote-topology
|
||||
patterns (upstream-only / github-fork / multi-remote).
|
||||
- [`docs/upstream-prs.md`](docs/upstream-prs.md) — how to send a PR
|
||||
upstream using the `~/dev/upstream-prs/` worktree flow. Includes
|
||||
a primer for anyone new to forks / pull requests.
|
||||
- [`docs/lnbits-upstream-flow.md`](docs/lnbits-upstream-flow.md) —
|
||||
reference for how `lnbits/lnbits` itself moves: `dev` / `main`
|
||||
branch model, squash-merge convention, release tagging.
|
||||
|
||||
## Contributing to this scaffold
|
||||
|
||||
Iteration happens under the sandboxed-claude policy from omnixy's
|
||||
`scripts/sandbox-claude.sh` — a per-launch `.claude/settings.json`
|
||||
|
|
|
|||
128
docs/lnbits-upstream-flow.md
Normal file
128
docs/lnbits-upstream-flow.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# `lnbits/lnbits` — Upstream Development Flow
|
||||
|
||||
Reference for [`github.com/lnbits/lnbits`](https://github.com/lnbits/lnbits).
|
||||
Verified against git history on 2026-05-18; bump the date when you
|
||||
re-verify, and patch this doc if the model has drifted.
|
||||
|
||||
## Branch model
|
||||
|
||||
Two long-lived branches:
|
||||
|
||||
| Branch | Role | How it moves |
|
||||
|---|---|---|
|
||||
| `dev` | Integration / staging | One squash-merge commit per PR. Linear. |
|
||||
| `main` | Release | A non-fast-forward merge of `dev` at each release. |
|
||||
|
||||
`main` never receives feature PRs directly. **Every change reaches
|
||||
`main` through `dev`.**
|
||||
|
||||
## PR flow → `dev`
|
||||
|
||||
1. Contributor opens a PR targeting `dev` (not `main`).
|
||||
2. Maintainer squash-merges via the GitHub UI.
|
||||
3. The resulting commit on `dev` has:
|
||||
- exactly one parent (linear history),
|
||||
- subject ending in `(#NNNN)` — the squash-merge signature,
|
||||
- lowercase conventional-commit prefix: `feat:`, `fix:`, `chore:`,
|
||||
`chore(deps):`, `docs:`, `ci:`, `test:`, `refactor:`.
|
||||
4. CI runs on the PR; nothing else is required between merge and the
|
||||
commit appearing on `dev`.
|
||||
|
||||
Example chain on `dev`:
|
||||
|
||||
```
|
||||
c9c68bd8 Fix: Use default reaction on bootstrap (#3965)
|
||||
810a1372 fix: tighten agents file (#3966)
|
||||
36d696b2 Fix: wrong use of `in` operator (#3960)
|
||||
8b426efa test: add pyinstrument profiler (#3955)
|
||||
```
|
||||
|
||||
Each is a single squashed commit. No merge commits inside `dev`.
|
||||
|
||||
## Release flow → `main`
|
||||
|
||||
When `dev` is ready to ship:
|
||||
|
||||
1. A release-candidate version bump lands on `dev` as a normal PR:
|
||||
`chore: update to version vX.Y.Z-rcN (#NNNN)`.
|
||||
2. Validation happens against the RC.
|
||||
3. A final version-bump PR lands on `dev`:
|
||||
`chore: update to version vX.Y.Z (#NNNN)`.
|
||||
4. A maintainer runs the release merge locally:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull --ff-only origin main
|
||||
git merge dev # true (non-FF) merge, default message
|
||||
git push origin main
|
||||
git tag vX.Y.Z # tag the merge commit or the bump commit
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
5. The resulting merge commit on `main` has:
|
||||
- **two parents** (prior `main` tip + `dev` tip),
|
||||
- subject exactly `Merge branch 'dev'` (git default when on `main`),
|
||||
- **not** authored via the GitHub PR-merge UI (that would produce
|
||||
`Merge pull request #N from …`).
|
||||
|
||||
Because `main` typically carries a stray release-bump commit that isn't
|
||||
on `dev`, the histories have diverged and git is forced into a true
|
||||
merge. `--no-ff` is not needed for that reason.
|
||||
|
||||
## Reading the history
|
||||
|
||||
```bash
|
||||
# Release log (one entry per release):
|
||||
git log main --first-parent --oneline
|
||||
|
||||
# Full changelog leading into the next release:
|
||||
git log dev --oneline
|
||||
|
||||
# Verify a merge is a true non-FF merge:
|
||||
git log <sha> -1 --format='%P' # two parent hashes = true merge
|
||||
```
|
||||
|
||||
## Diagram
|
||||
|
||||
```
|
||||
(PRs squash-merged one at a time)
|
||||
│
|
||||
▼
|
||||
dev: ── A ── B ── C ── D ── E ── F (rc1) ── G ── H (v1.5.4)
|
||||
╲
|
||||
╲ (true merge)
|
||||
╲
|
||||
main: ────────── prev release ─────────────── X ─────────── M
|
||||
▲
|
||||
│
|
||||
Merge branch 'dev'
|
||||
tag: v1.5.4
|
||||
```
|
||||
|
||||
## Implications for contributors
|
||||
|
||||
- **Base PRs on `dev`.** PRs against `main` will not be accepted.
|
||||
- **Use lowercase conventional-commit titles.** Verified stable across
|
||||
the last 25+ merged PRs.
|
||||
- **Don't expect `main` to move between releases.** It only advances
|
||||
when a maintainer cuts a release merge.
|
||||
- **Tagging is on `main`.** Consumers pinning to a tag get the
|
||||
released state, never a `dev` snapshot.
|
||||
|
||||
## Adapting this in your own fork
|
||||
|
||||
If you maintain a long-lived fork of `lnbits/lnbits` for production use,
|
||||
you'll likely want to:
|
||||
|
||||
1. Mirror upstream's `dev` / `main` split — easier to track upstream
|
||||
merges back into your fork.
|
||||
2. Adopt a version-suffix convention that surfaces fork identity in
|
||||
tags and the packaged `pyproject.toml` version, e.g.
|
||||
`v<upstream>-<your-tag>.<N>`. This makes it unambiguous in logs and
|
||||
deployed-package metadata that you're running fork-modified code.
|
||||
3. Decide whether you also need pre-release channels (`-rcN`, `-devN`)
|
||||
on top of upstream's; useful if your fork ships to staging hosts
|
||||
before promotion.
|
||||
|
||||
Specifics depend entirely on your team's needs — this scaffold
|
||||
deliberately doesn't prescribe a fork-versioning scheme.
|
||||
138
docs/upstream-prs.md
Normal file
138
docs/upstream-prs.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# Contributing back to upstream
|
||||
|
||||
How to send fixes or features upstream (to projects like `lnbits/lnbits`)
|
||||
using the worktree-based PR flow built into this scaffold's dev-env
|
||||
layer.
|
||||
|
||||
> **New to forks and pull requests?** Skim the [Primer](#primer) first.
|
||||
> Otherwise jump to [Workflow](#workflow).
|
||||
|
||||
## Primer
|
||||
|
||||
GitHub, Forgejo, Gitea, and similar services host *git repositories*.
|
||||
To contribute to a project you don't own:
|
||||
|
||||
1. **Fork** the upstream repo via the web UI ("Fork" button). This
|
||||
creates `<host>/<your-user>/<repo>` — your personal copy.
|
||||
2. **Branch** off your fork's `main` (or whichever branch upstream
|
||||
accepts PRs against). Make your change.
|
||||
3. **Push** the branch to your fork.
|
||||
4. **Open a Pull Request** from your fork's branch into the upstream.
|
||||
5. **Iterate** — maintainers review, you push follow-up commits to the
|
||||
same branch and the PR updates automatically.
|
||||
6. **Maintainer merges** — usually as a single squashed commit on
|
||||
upstream's target branch.
|
||||
7. **Delete the branch** on both sides once merged.
|
||||
|
||||
The three roles a remote can play in your local clone:
|
||||
|
||||
| Remote name | What it points at | Read or write? |
|
||||
|---|---|---|
|
||||
| `upstream` | The canonical project you're contributing to | Read (you fetch from it) |
|
||||
| `github-fork` (or `fork`) | Your personal copy you push to | Write (you push to it) |
|
||||
| `origin` | Optional: a private mirror you control (forgejo / gitea / codeberg / sourcehut) | Read + write |
|
||||
|
||||
This scaffold's `settings.nix` declares all three via `git.remotes`;
|
||||
the bootstrap script wires them into the local bare repo so every
|
||||
worktree sees the same set automatically. See
|
||||
[remotes.md](remotes.md) for topology patterns.
|
||||
|
||||
## Prerequisites (per project)
|
||||
|
||||
1. **Fork** the upstream repo on the relevant host.
|
||||
2. **Set your fork URL** in `settings.nix`:
|
||||
|
||||
```nix
|
||||
git.remotes.fork = "https://github.com/<your-username>/lnbits";
|
||||
```
|
||||
|
||||
Use the SSH form (`git@github.com:<your-username>/lnbits.git`) if
|
||||
you've set up SSH push.
|
||||
|
||||
3. **Bootstrap** (or re-bootstrap). The (planned) bootstrap script
|
||||
adds the `github-fork` remote to the bare repo at
|
||||
`~/dev/repos/lnbits.git`.
|
||||
|
||||
Verify the remotes are wired:
|
||||
|
||||
```sh
|
||||
git -C ~/dev/repos/lnbits.git remote -v
|
||||
```
|
||||
|
||||
Expected at minimum:
|
||||
|
||||
```
|
||||
upstream https://github.com/lnbits/lnbits (fetch / push)
|
||||
github-fork https://github.com/<your-user>/lnbits (fetch / push)
|
||||
```
|
||||
|
||||
If `origin` is set, it points at your private remote (forgejo etc.).
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Create the PR branch
|
||||
|
||||
```sh
|
||||
prb lnbits fix-invoice-validation
|
||||
```
|
||||
|
||||
The (planned) `prb` helper:
|
||||
|
||||
- `git fetch upstream`
|
||||
- creates branch `fix-invoice-validation` off `upstream/main`
|
||||
- adds a worktree at `~/dev/upstream-prs/lnbits-fix-invoice-validation`
|
||||
- sets the worktree's push target to your fork (`github-fork`)
|
||||
|
||||
### 2. Make changes
|
||||
|
||||
```sh
|
||||
cd ~/dev/upstream-prs/lnbits-fix-invoice-validation
|
||||
# … edit, test, commit …
|
||||
git commit -am "Fix invoice validation for zero-amount invoices"
|
||||
```
|
||||
|
||||
### 3. Push to your fork + open the PR
|
||||
|
||||
```sh
|
||||
git push -u github-fork fix-invoice-validation
|
||||
```
|
||||
|
||||
git prints a URL to open the PR. Alternatively, with the GitHub CLI:
|
||||
|
||||
```sh
|
||||
gh pr create --base main --head <your-username>:fix-invoice-validation
|
||||
```
|
||||
|
||||
(Use the project's preferred target branch — for `lnbits/lnbits` it's
|
||||
`dev`, not `main`. See [lnbits-upstream-flow.md](lnbits-upstream-flow.md).)
|
||||
|
||||
### 4. Iterate on review
|
||||
|
||||
Push more commits to the same branch — the PR updates automatically.
|
||||
Don't force-push unless you've squashed/rebased deliberately.
|
||||
|
||||
### 5. After merge — clean up
|
||||
|
||||
```sh
|
||||
prc lnbits fix-invoice-validation
|
||||
```
|
||||
|
||||
The (planned) `prc` helper removes the worktree and prunes the local
|
||||
branch. On the web UI, click "Delete branch" on the merged PR to
|
||||
remove your fork's copy too.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **PR target branch.** Many projects (including `lnbits/lnbits`)
|
||||
accept PRs against `dev`, not `main`. Read the project's
|
||||
CONTRIBUTING file or its branch-model reference before opening.
|
||||
- **Stale branches on your fork.** They accumulate forever if you
|
||||
don't clean up. `prc` handles the local side; the web UI handles
|
||||
the fork side.
|
||||
- **Commits drifting onto your fork's `main`.** Keep your fork's
|
||||
`main` strictly in lockstep with `upstream/main`. All feature
|
||||
work lives in branches.
|
||||
- **Wrong remote when pushing.** If `git push` defaults to `upstream`
|
||||
(because that's `origin`), you'll get an access-denied error. The
|
||||
worktree's tracking remote should be `github-fork`; if not, set it:
|
||||
`git branch --set-upstream-to=github-fork/<branch>`.
|
||||
Reference in a new issue