4.7 KiB
Versioning and releases
Git hooks and versioning
scripts/pre-commit bumps the patch number of version in plugin.json on every commit
(1.0.0 → 1.0.1) and stages it into that same commit. scripts/post-commit re-stages
plugin.json afterwards, because commits of a file selection (git commit -- <paths>, which
some IDEs use) are built from a temporary index and would otherwise leave it looking modified.
The hooks enable themselves. Git never activates hooks from repository content, so a
fresh clone, or a repo created from this template, starts without them. The first run of
tools/bundle.py fixes that by running git config core.hooksPath scripts, and every
workflow runs it: directly, or through scripts/deploy.sh and scripts/watch.sh. That
includes --check, so after cloning run
python3 tools/bundle.py --check
once, before your first commit. A commit made before any build runs without the hooks.
bundle.py leaves core.hooksPath alone when it is already set (so pointing it elsewhere on
purpose sticks), skips the step in CI (whenever CI is set) and outside a git work tree, and
never fails a build over it. To turn the hooks off for a clone, run
git config core.hooksPath .git/hooks; git config --unset core.hooksPath only lasts
until the next build.
The bump is skipped when:
- the commit message contains
[no bump],[skip bump],[no version bump]or[skip version](case-insensitive;nobump,no-bumpandno_bumpalso work), NO_BUMP=1(orGIT_NO_BUMP=1) is set, e.g.NO_BUMP=1 git commit, which is the only way for commits written in an editor, since their message doesn't exist yet when the hook runs,versioninplugin.jsonalready differs fromHEAD(you bumped it by hand, e.g. for a minor or major release),- a merge, rebase, cherry-pick or revert is in progress.
Because every version change publishes a release (see Publishing a release), a normal commit + push to
main ships a new patch release. Use a stop word for commits that should not.
Publishing a release
.gitea/workflows/release.yml builds, verifies and publishes the .plugin to the repo's
Releases tab on every push to main. dist/ is gitignored, so the artifact is always built
by CI rather than committed. Nothing in the workflow is plugin-specific — it reads the id and
version straight out of plugin.json, so it keeps working after you rename the plugin.
The version field in plugin.json is the single source of truth:
Push to main with… |
Result |
|---|---|
a new version |
creates tag v<version> and a new release |
the same version |
rebuilds the existing release, replacing its attachment |
So every push leaves a downloadable build attached to the current version, and bumping
version is what cuts a new release. With the git hooks enabled every commit bumps it,
so every push of a normal commit cuts a new patch release. Pushing a v* tag by hand still works and uses that
tag verbatim, as does a manual run (Actions → Publish plugin → Run workflow).
Every path runs --check, builds with --release, and gates the upload on
verify_bundle.py, so a bundle that fails verification is never published. A rebuild
replaces the attachment rather than piling up duplicates, and refreshes the release notes to
name the commit it came from. The tag itself is never moved: it keeps pointing at the commit
that first introduced that version.
Authentication uses secrets.API_TOKEN, an organisation-level Actions secret holding a user
token with the write:repository scope (which covers releases). Set it once under
Org → Settings → Actions → Secrets and every repo in the organisation inherits it; a
repo-level secret of the same name takes precedence if one repo ever needs different
credentials.
The workflow passes it to release.py as RELEASE_TOKEN, not GITEA_TOKEN. The Actions
runner injects its own GITEA_TOKEN into every step, and that value overrides a step-level
env: entry of the same name, so the org secret would be silently ignored.
If the org secret does not reach a repository the publish still works: release.py tries
RELEASE_TOKEN first and then falls back to the runner's GITEA_TOKEN, which can publish
releases in its own repository, and prints which one it used. An organisation secret is only
exposed to a repository that has Actions enabled, so check Repo → Settings → Actions if a
repo unexpectedly falls back.
If you would rather not manage a token at all, that injected GITEA_TOKEN can create
releases in its own repo: pass --token-env GITEA_TOKEN and drop the org secret. It is
scoped to the single job and expires with it, so it is the safer default; API_TOKEN is what
you want when publishing needs to reach beyond this repo.