diff --git a/README.md b/README.md index 8030ac6..cb0b93d 100644 --- a/README.md +++ b/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` diff --git a/docs/lnbits-upstream-flow.md b/docs/lnbits-upstream-flow.md new file mode 100644 index 0000000..3284f69 --- /dev/null +++ b/docs/lnbits-upstream-flow.md @@ -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 -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-.`. 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. diff --git a/docs/upstream-prs.md b/docs/upstream-prs.md new file mode 100644 index 0000000..f1816fa --- /dev/null +++ b/docs/upstream-prs.md @@ -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 `//` — 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//lnbits"; + ``` + + Use the SSH form (`git@github.com:/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//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 :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/`.