No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
bramkolkman fb560ec730
All checks were successful
pull request / check (pull_request) Successful in 1s
pull request / commits (pull_request) Successful in 0s
pull request / describe (pull_request) Successful in 2s
pull request / pull-request (pull_request) Successful in 0s
Merge pull request 'feat: conventional workflows' (#1) from feat/conventional-workflows into develop
Reviewed-on: #1
2026-09-14 13:42:03 +00:00
.forgejo/workflows feat: release this repository with its own workflows 2026-09-14 15:38:36 +02:00
.gitignore feat: add the conventional commits workflow 2026-09-14 10:38:57 +02:00
README.md feat: release this repository with its own workflows 2026-09-14 15:38:36 +02:00

CI

Reusable Forgejo Actions workflows for the repositories on git.thinkerium.nl.

Pinning

Reference them at @v1. That tag moves to each release on the major line, so fixes arrive without a commit in every repository, and a breaking change to an input announces itself by landing on @v2 instead. Pin an exact @v1.4.2 when a repository needs to stay put; @main is for this repository only, which uses its own workflows to test them before anything else consumes them.

Branch model

The workflows assume, but do not require, two protected branches:

fix/expired-certs ──────────────────────────┐
                                            ▼
feat/tokens ──► develop ──────────────────► main ──► release
fix/parser  ──►

Work reaches develop through pull requests, develop reaches main through one more, and merging that one cuts a release. Urgent work goes straight to main through its own pull request and is released on its own.

Both branch names are inputs, so master and development work as well. A repository with no development branch is fine too: nothing matches, and each pull request into the main branch becomes its own release entry.

One constraint is not negotiable. The merge from the development branch into the main branch must be a merge commit — squash it and every pull request boundary inside it is destroyed, with no way to recover the list afterwards. Restrict the merge style on the main branch accordingly. Pull requests into the development branch can be squashed or merged freely.

Conventional commits

Checks that the commits a pull request adds follow the conventional commit specification, using Cocogitto. Merge commits are ignored; fixup commits are not.

Add to a repository as .forgejo/workflows/pull-request.yml:

name: pull request

on:
  pull_request:

jobs:
  commits:
    uses: thinkerium/ci/.forgejo/workflows/conventional-commits.yml@v1

Optional input: cocogitto-version, default 7.0.0.

Conventional pull request

Writes the title and the description of a pull request from the commits it adds. Open the pull request yourself; this keeps it current from then on.

The title is a conventional commit, derived in order of preference from:

  1. the branch name, when it reads as type(scope)/slug — fix(auth)/expired-tokens becomes fix(auth): expired tokens;
  2. the commit subject, when the branch adds exactly one commit;
  3. the most significant type on the branch plus the branch name, as a fallback that always yields something valid.

It is written once, when the pull request is opened. Later pushes leave it alone, so a title you rewrite by hand stays rewritten — at the cost of going stale if a later commit changes the type.

The description is a Cocogitto changelog of the commits between the merge base and the branch tip, written between markers:

Anything you write above is kept.

<!-- cog:changelog:start -->
### Features

- **api**: add the /tokens endpoint (deadeb9)
<!-- cog:changelog:end -->

So is anything you write below.

Only the text between the markers is replaced. Write notes either side of the block and they survive; write them inside and the next push overwrites them. If one marker is deleted without the other the job fails rather than guess, since appending a second block would leave a changelog that never updates again.

Add to the same .forgejo/workflows/pull-request.yml:

jobs:
  pull-request:
    uses: thinkerium/ci/.forgejo/workflows/conventional-pull-request.yml@v1

The release pull request

A pull request from the development branch into the main branch is the release itself. Its branch name says nothing about what is in it, and what is in it is every pull request merged since the last release — so it is titled chore(release): develop into main and described by that list rather than by its commits:

### Pull requests in this release

- feat(api): add search (#32)

Back merges from the main branch are left out, since they carry what the main branch released already.

Optional inputs: main-branch, default main; development-branch, default develop; cocogitto-version, default 7.0.0; template, a path to a Tera changelog template in your own repository, default a template written for pull request descriptions.

Commits that are not conventional are skipped rather than failing the job — conventional-commits is what reports those. A branch with none of them gets a placeholder description.

Two limitations worth knowing. Pull requests from forks are skipped, because they run with a read-only token. And the built-in FORGEJO_TOKEN only reaches its own repository, which is all this needs.

Release

Tags and publishes a release on every push to the main branch, with notes listing the pull requests it contains. Nothing is ever pushed to the branch: the version is derived from the commits, and the tag and the release are both created over the API.

Add to a repository as .forgejo/workflows/release.yml:

name: release

on:
  push:
    branches:
      - main

jobs:
  release:
    uses: thinkerium/ci/.forgejo/workflows/release.yml@v1

The notes are one entry per pull request, grouped by type. A merge from the development branch is expanded into the requests it carries, so the release reads as the work it ships rather than as the one merge that delivered it:

### Features

- **api**: add search (#32)

### Bug Fixes

- **tls**: renew the certificates (#30)

The version comes from Cocogitto, derived from the conventional commit types of those entries — a feat takes a minor, a fix a patch, and a breaking change a major once past 1.0.0.

Tag prefixes

Nothing is asked of the calling repository. Cocogitto only recognises a tag as a version when it knows the prefix, and answers with a brand new 0.1.0 rather than an error when it does not, so the workflow works the prefix out and hands cocogitto a configuration of its own:

  1. a tag_prefix in your cog.toml, if you keep one, always wins;
  2. otherwise the prefix comes from the version tags already in the repository, so v1.4.2 keeps tagging v, and 1.4.2 keeps tagging bare;
  3. only a repository with no release yet falls back to the tag-prefix input, which defaults to v.

Your own cog.toml is read either way, so custom commit types and changelog settings still apply — the prefix is prepended to a copy, never written into your repository.

Note that if you do keep a cog.toml, tag_prefix belongs at the top level; under [bump] cocogitto fails to parse the file.

Nothing is released when the push brings no new commits, or when everything in it is a chore or a docs — the job succeeds and does nothing.

By default a major tag (v1) is moved to each release as well, so that consuming repositories can track a major line instead of pinning an exact version. Below 1.0.0 a breaking change is only a minor bump, so v0 moves across breaking changes; pin exactly if that matters. Turn it off with major-tag: false.

A repository with no releases yet starts at 0.1.0, cocogitto's default. Set initial-version: "1.0.0" to declare the interface stable from the outset — worth doing when other repositories build on it, since past 1.0.0 a breaking change costs a major rather than a minor. It is read only when there are no tags to count from, and never decides whether to release.

Optional inputs: main-branch, default main; development-branch, default develop; major-tag, default true; initial-version, default unset; tag-prefix, default v; cocogitto-version, default 7.0.0; template, a path to a Tera changelog template in your own repository.

How the notes are built

Cocogitto reads commits, and has no way to walk only the first parent, so a release of twelve commits across three pull requests would read as twelve entries. The workflow therefore walks the first parent itself, recovers each pull request title from the merge commit Forgejo wrote, replays the result onto a scratch branch as one commit per request, and points cocogitto at that. The scratch branch is never pushed — which is also why the entries carry no commit hash, since those hashes would not exist anywhere after the job ends.