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>
4.7 KiB
lnbits/lnbits — Upstream Development Flow
Reference for 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
- Contributor opens a PR targeting
dev(notmain). - Maintainer squash-merges via the GitHub UI.
- The resulting commit on
devhas:- 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:.
- 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:
-
A release-candidate version bump lands on
devas a normal PR:chore: update to version vX.Y.Z-rcN (#NNNN). -
Validation happens against the RC.
-
A final version-bump PR lands on
dev:chore: update to version vX.Y.Z (#NNNN). -
A maintainer runs the release merge locally:
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 -
The resulting merge commit on
mainhas:- two parents (prior
maintip +devtip), - subject exactly
Merge branch 'dev'(git default when onmain), - not authored via the GitHub PR-merge UI (that would produce
Merge pull request #N from …).
- two parents (prior
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
# 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 againstmainwill not be accepted. - Use lowercase conventional-commit titles. Verified stable across the last 25+ merged PRs.
- Don't expect
mainto 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 adevsnapshot.
Adapting this in your own fork
If you maintain a long-lived fork of lnbits/lnbits for production use,
you'll likely want to:
- Mirror upstream's
dev/mainsplit — easier to track upstream merges back into your fork. - Adopt a version-suffix convention that surfaces fork identity in
tags and the packaged
pyproject.tomlversion, e.g.v<upstream>-<your-tag>.<N>. This makes it unambiguous in logs and deployed-package metadata that you're running fork-modified code. - 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.