Release process

Guidestable
On this page

This page describes how coprctl is released. It covers the versioning model, the two kinds of release (normal and NVR), and the workflow for packaging-only changes.

The release pipeline is automated with Release Please (GitHub Actions) and GoReleaser, and Copr builds are triggered from the repository via the SCM source with auto-rebuild. The full workflow lives in .github/workflows/release.yml.

Versioning model

There is one source of truth for the version: the Release Please manifest (.release-please-manifest.json), which drives a semantic version (0.1.0, 0.2.0, ...). Release Please owns:

  • the semantic version and the v<semver> git tag,
  • the CHANGELOG.md and the GitHub release,
  • and, via the extra-files configuration, the Version: line in coprctl.spec, so the RPM version always matches the semantic version.

The tag is a bare v<semver> (for example v0.4.0) so GoReleaser can parse it as a semantic version. Copr's tag-triggered rebuild matches the package via a package-scoped webhook URL (which names the package), so the tag does not need to encode the package name.

The RPM Release field is separate and independent. It is how Copr distinguishes multiple builds of the same version. This gives two release paths, described below.

Release Please conventions

Because Release Please derives the next version from conventional commits, the repo follows these rules:

  • Pull request only. All changes land on main through a pull request, never by pushing directly to main.
  • Squash merge. Merge with a squash merge so each merged PR contributes exactly one commit with a conventional-commit message. This is what Release Please parses to compute the next version and build the changelog.
  • Conventional commits. The merged commit message determines the bump: fix: bumps the patch, feat: bumps the minor, and a breaking change (a ! or BREAKING CHANGE: body) bumps the major. Changelog sections are grouped from these prefixes.

Release Please opens a release pull request (titled chore: release <branch>) that bumps the version, updates coprctl.spec Version:, and rewrites CHANGELOG.md. Merging that PR publishes the v<semver> tag and the GitHub release, which triggers the GoReleaser build.

Normal releases

A normal release publishes a new semantic version:

  1. Merge feature and fix pull requests to main with squash merges and conventional-commit messages.
  2. Release Please opens a release PR that bumps the version and the spec Version: field.
  3. Review and squash-merge the release PR.
  4. The v<semver> tag is created and the GitHub release is published.
  5. GoReleaser builds and uploads artifacts (tarballs and checksums).
  6. Copr rebuilds coprctl from main (auto-rebuild on the SCM source), so dnf copr enable abn/coprctl && dnf install coprctl installs the new version.

The RPM version equals the semantic version: coprctl-<version>-1.fcNN.

NVR releases

An NVR (name-version-release) release republishes the same version with a different release number, without a new semantic version. Use this to fix a packaging problem (a spec bug, a missing BuildRequires, a wrong build flag) without bumping the application version.

The RPM Release field starts at 1 and is bumped manually:

  1. Edit coprctl.spec and increment Release: (for example 1%{?dist} to 2%{?dist}).
  2. Commit and open a pull request. Use a conventional-commit message such as fix(packaging): ... so it is captured correctly.
  3. Squash-merge the PR.
  4. Push a tag in N-V-R form: coprctl-<version>-<release> (for example coprctl-0.4.7-2). The webhook is tag-only, so a plain branch push does not rebuild; the tag is the trigger.

The tag fires the same package-scoped webhook as a release tag and Copr rebuilds the package, producing coprctl-<version>-2.fcNN. The semantic version and the v<semver> tag are unchanged. NVR releases are common and inexpensive; there is no need to bump the version for a packaging fix.

Why the tag: Copr matches a tag to a package either through the package-scoped webhook URL (which names the package) or through the N-V/N-V-R shape of the tag itself. A bare v<semver> tag cannot be reused for an NVR release, so the N-V-R form carries both the version and the release that the webhook should build.

Packaging-only changes

A change that touches only the packaging (the spec, the manifest, the GoReleaser or release config) still goes through the same PR-only, squash-merge flow. Decide which release path applies:

  • If the change only affects how the package is built (a build flag, a dependency, a Source0 change), it is an NVR release: bump Release:, merge, and push the N-V-R tag so Copr rebuilds.
  • If the change is a user-facing feature or fix (new command, changed behavior), it is a normal release: merge with feat:/fix: and let Release Please open the version-bumping release PR.

There is no separate branch or process for packaging-only work; it reuses the same pull request and squash-merge convention.

Working with Release Please

  • Do not edit CHANGELOG.md or the version by hand. Release Please owns both. Edit only through its release PR.
  • Keep coprctl.spec's Version: in sync. The Version: tag sits inside a release-please version block (x-release-please-start-version ... end comment lines). The markers are plain spec comments so rpmbuild parses the tag cleanly; do not move them back onto the Version: line as a trailing comment, which rpmbuild rejects with "Tag takes single token only".
  • Respect the manifest. .release-please-manifest.json records the last released version. Do not edit it by hand except during bootstrap.
  • Preview the next release. Run release-please release-pr locally (or in CI) to see the proposed bump before merging.