diff --git a/README.md b/README.md index f035984..7c15a6b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty) -Auto-tag your GitLab or GitHub repository with semantic version tags from CI — one tool, two strategies, two providers. +Auto-tag your GitLab or GitHub repository with semantic version tags from CI. One tool covers both providers and offers two bump strategies. ## Install @@ -51,8 +51,8 @@ semvertag: ``` It runs `uvx semvertag tag` against your repo on the default branch. -semvertag inspects the head commit + tag history, decides the -appropriate semver bump, and creates the new tag via the GitLab API. +semvertag inspects the head commit and the tag history, decides the +semver bump, and creates the new tag through the GitLab API. > A one-line `include: - component: …` via the GitLab CI Catalog will > replace this snippet once the component is published. For now, paste @@ -86,28 +86,29 @@ GitHub Enterprise setup, outputs, and troubleshooting. ## Strategies -- **branch-prefix** (default): the head commit on the default branch - must be a merge commit whose subject names a `feature/` (minor), - `bugfix/`, or `hotfix/` (patch) branch. -- **conventional-commits**: parses the head commit's - [Conventional Commits](https://www.conventionalcommits.org/) - header (`feat:` minor, `fix:`/`perf:` patch, `!` or `BREAKING - CHANGE:` major). +The default `branch-prefix` strategy requires the head commit on the +default branch to be a merge commit whose subject names a `feature/` +(minor), `bugfix/`, or `hotfix/` (patch) branch. -Both are configurable via env vars. See [docs](https://semvertag.modern-python.org) +The `conventional-commits` strategy parses the head commit's +[Conventional Commits](https://www.conventionalcommits.org/) +header (`feat:` minor, `fix:`/`perf:` patch, `!` or `BREAKING +CHANGE:` major). + +Both are configurable through environment variables. See [docs](https://semvertag.modern-python.org) for the full configuration surface. ## Built with semvertag stands on other `modern-python` libraries: -- **[modern-di-typer](https://github.com/modern-python/modern-di-typer)** — - dependency-injection wiring for the Typer CLI. semvertag resolves its +- [modern-di-typer](https://github.com/modern-python/modern-di-typer) wires + dependency injection into the Typer CLI. semvertag resolves its settings, API providers, and bump strategies through a `modern_di` container ([`semvertag/ioc.py`](https://github.com/modern-python/semvertag/blob/main/semvertag/ioc.py)). -- **[httpware](https://github.com/modern-python/httpware)** — the resilient - HTTP client both providers use for the GitLab/GitHub REST calls (retries, - timeouts, typed decoding, secret redaction). +- [httpware](https://github.com/modern-python/httpware) is the HTTP client + both providers use for the GitLab/GitHub REST calls. It handles retries, + timeouts, typed decoding, and secret redaction. ## 📚 [Documentation](https://semvertag.modern-python.org) @@ -118,4 +119,4 @@ semvertag stands on other `modern-python` libraries: ## Part of `modern-python` Browse the full list of templates and libraries in -[`modern-python`](https://github.com/modern-python) — see the org profile for the categorized index. +[`modern-python`](https://github.com/modern-python). The org profile has the categorized index. diff --git a/docs/assets/social-card.png b/docs/assets/social-card.png index 14a6722..11c9c84 100644 Binary files a/docs/assets/social-card.png and b/docs/assets/social-card.png differ diff --git a/docs/index.md b/docs/index.md index c22cfd9..34a10c7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,13 +7,13 @@ -Auto-tag your GitLab repository with semantic version tags from CI — -one tool, two strategies. +Auto-tag your GitLab repository with semantic version tags from CI, +using one of two bump strategies. -semvertag reads the head commit and tag history from your GitLab -project via the API, decides the appropriate semver bump based on the -strategy you've configured, and creates the new git tag — all from a -single command in your CI pipeline. +From a single command in your CI pipeline, semvertag reads the head +commit and tag history from your GitLab project through the API, +decides the semver bump with the strategy you've configured, and +creates the new git tag. ## Quick start @@ -52,13 +52,13 @@ SEMVERTAG_PROJECT_ID= \ semvertag ships with two bump-decision strategies: -- [**branch-prefix**](strategies/branch-prefix.md) — bump based on the - source branch of the latest merge commit (`feature/` → minor, - `bugfix/` / `hotfix/` → patch). The default. -- [**conventional-commits**](strategies/conventional-commits.md) — - bump based on the head commit's Conventional Commits message +- [branch-prefix](strategies/branch-prefix.md), the default, bumps + based on the source branch of the latest merge commit (`feature/` → minor, + `bugfix/` / `hotfix/` → patch). +- [conventional-commits](strategies/conventional-commits.md) bumps + based on the head commit's Conventional Commits message (`feat:` → minor, `fix:` / `perf:` → patch, `!` or `BREAKING CHANGE:` → major). -Both strategies are configurable via environment variables — see the -strategy pages for the full configuration surface. +Both strategies are configurable through environment variables. The +strategy pages cover the full configuration surface. diff --git a/docs/providers/github.md b/docs/providers/github.md index 017a6d9..1b84659 100644 --- a/docs/providers/github.md +++ b/docs/providers/github.md @@ -6,14 +6,14 @@ Use semvertag in GitHub Actions via the published composite action fallback for environments that can't consume the action lives at the bottom of this page. -## Quick Start +## Quick start -The minimum useful workflow: auto-tag on every push to the default +The minimum useful workflow auto-tags on every push to the default branch. -> **Required setup.** Either rely on the workflow-scoped -> `GITHUB_TOKEN` (which is auto-issued per job) — in which case the -> workflow MUST declare `permissions: contents: write` — OR provide a +> The job needs a token with write access. Either rely on the +> workflow-scoped `GITHUB_TOKEN`, which is auto-issued per job, and +> declare `permissions: contents: write` in the workflow, or provide a > fine-grained PAT with `contents: write` (single repo) or a classic > PAT with `repo` / `public_repo` scope. Store the PAT as a repo > secret named `SEMVERTAG_TOKEN`; the alias chain picks it up ahead @@ -40,19 +40,18 @@ a bump is warranted by the configured strategy, creates a new tag ref via the GitHub API. If no bump is warranted, the job exits 0 without pushing. -> **First tag.** semvertag bumps from the highest existing semver tag +> semvertag bumps from the highest existing semver tag > and never creates the first one. It reads only plain semver tags such > as `0.1.0`; a `v` prefix (`v0.1.0`) does not parse and is ignored. Until > one exists, every run reports `no_tags` and exits 0. Push one with > `git tag 0.1.0 && git push origin 0.1.0`. -> **Auto-detection.** semvertag detects GitHub Actions from the -> `GITHUB_ACTIONS=true` env var that GHA sets automatically. The -> `--provider` flag is therefore optional inside GHA — explicit -> `--provider github` is only needed when running outside GHA (e.g. -> on a developer laptop targeting a github.com repo). +> semvertag detects GitHub Actions from the `GITHUB_ACTIONS=true` env +> var that GHA sets automatically, so the `--provider` flag is optional +> inside GHA. Pass `--provider github` explicitly only when running +> outside GHA (e.g. on a developer laptop targeting a github.com repo). -> **No checkout needed.** semvertag reads the head commit and the tag +> semvertag reads the head commit and the tag > history over the GitHub API and never touches the working tree, so > the job needs neither an `actions/checkout` step nor a `fetch-depth` > setting. Add a checkout only if other steps in the same job need the @@ -73,8 +72,8 @@ Pass `--strategy` (or set `SEMVERTAG_STRATEGY`) to one of: strategy: conventional-commits ``` -> **Strategy-specific env vars** (e.g. `SEMVERTAG_BRANCH_PREFIX__MINOR`) -> remain configured on the calling step. The composite action only +> Strategy-specific env vars (e.g. `SEMVERTAG_BRANCH_PREFIX__MINOR`) +> stay configured on the calling step. The composite action only > explicitly sets `GITHUB_TOKEN` and `SEMVERTAG_STRATEGY`; every other > env var on the calling step passes through to the action's run step. > @@ -86,7 +85,7 @@ Pass `--strategy` (or set `SEMVERTAG_STRATEGY`) to one of: ## Required permissions -The job creates a tag ref, so the token it uses MUST carry write +The job creates a tag ref, so the token it uses must carry write access to the repository's contents. semvertag reads the token from these env vars in order: `SEMVERTAG_GITHUB__TOKEN`, `SEMVERTAG_TOKEN`, `GITHUB_TOKEN`. The @@ -100,7 +99,7 @@ When you give the step an `id:`, downstream steps can read three outputs: |---|---| | `tag` | The created tag (e.g. `1.2.3`), or empty string when `status` is `no-bump`. | | `bump` | `none` \| `patch` \| `minor` \| `major`. | -| `status` | `created` (tag pushed) \| `no-bump` (nothing to tag — no prior tag, already tagged, no merge commit, or non-conforming commit). On CLI error the action itself exits non-zero and this output is not written. | +| `status` | `created` (tag pushed) \| `no-bump` (nothing to tag: no prior tag, already tagged, no merge commit, or non-conforming commit). On CLI error the action itself exits non-zero and this output is not written. | Example: trigger a downstream release-notes job only when a tag was created. @@ -127,7 +126,7 @@ jobs: ## Preview the next bump -Pass `dry-run: true` to compute the bump without pushing a tag — useful in +Pass `dry-run: true` to compute the bump without pushing a tag. Use it in CI smoke tests, in PR previews, or to see what the next release would be: ```yaml @@ -158,35 +157,33 @@ Output (example): Three cases govern which token the job should use: -- **Workflow-scoped `GITHUB_TOKEN`** (preferred for most projects). +- The workflow-scoped `GITHUB_TOKEN` is preferred for most projects. GitHub Actions issues a fresh token per job; it inherits the workflow's `permissions:` block. Add `permissions: contents: write` at the workflow level (as in the snippet above). The token is auto-exported as `GITHUB_TOKEN` and picked up by the alias chain. -- **Fine-grained PAT scoped to the single repository.** Required - scope: `Contents: Read and write`. Store as a repo secret named +- A fine-grained PAT scoped to the single repository needs the + `Contents: Read and write` scope. Store it as a repo secret named `SEMVERTAG_TOKEN`; the alias chain picks it up ahead of `GITHUB_TOKEN`. Use this when the workflow runs across organizations or needs scopes the workflow token can't grant. -- **Classic PAT.** Required scope: `repo` (private repos) or - `public_repo` (public repos only). Same storage shape as the - fine-grained PAT. Less preferred — classic PATs bleed scope - across all of the user's repos. - -> **Masking caveat.** Because the alias chain reads -> `SEMVERTAG_GITHUB__TOKEN` → `SEMVERTAG_TOKEN` → `GITHUB_TOKEN` in -> order and the first set value wins, a stale `SEMVERTAG_TOKEN` left -> over from a prior PAT-based setup will silently override the -> workflow's `GITHUB_TOKEN`. If you migrate from PAT → -> workflow-token, unset `SEMVERTAG_TOKEN` from the repo's secrets. - -**GitHub Enterprise**: set `SEMVERTAG_GITHUB__ENDPOINT` (note the -double underscore — pydantic-settings uses `__` as the nested-key -delimiter, so `SEMVERTAG_GITHUB_ENDPOINT` with a single underscore is -silently ignored) as a workflow-level env or a repo secret pointing -to the instance's API root, e.g. -`https://github.example.com/api/v3`. The default is -`https://api.github.com`. +- A classic PAT needs the `repo` (private repos) or `public_repo` + (public repos only) scope and is stored the same way as the + fine-grained PAT. It is less preferred because classic PATs bleed + scope across all of the user's repos. + +> The alias chain reads `SEMVERTAG_GITHUB__TOKEN` → `SEMVERTAG_TOKEN` +> → `GITHUB_TOKEN` in order and the first set value wins, so a stale +> `SEMVERTAG_TOKEN` left over from a prior PAT-based setup will +> silently override the workflow's `GITHUB_TOKEN`. If you migrate from +> PAT → workflow-token, unset `SEMVERTAG_TOKEN` from the repo's secrets. + +For GitHub Enterprise, set `SEMVERTAG_GITHUB__ENDPOINT` as a +workflow-level env or a repo secret pointing to the instance's API +root, e.g. `https://github.example.com/api/v3`. The default is +`https://api.github.com`. Note the double underscore: pydantic-settings +uses `__` as the nested-key delimiter, so `SEMVERTAG_GITHUB_ENDPOINT` +with a single underscore is silently ignored. For most consumers on `github.com`-hosted repos with the workflow-scoped `GITHUB_TOKEN`, the minimal workflow snippet above @@ -218,10 +215,10 @@ for the full type-to-bump mapping. ## Without the composite action -If your environment can't consume the action — GitHub Enterprise +If your environment can't consume the action (GitHub Enterprise instances without Marketplace access, security-constrained orgs that forbid third-party actions, or anyone who wants explicit control over -the uv install step — paste the pure-CLI recipe instead: +the uv install step), paste the pure-CLI recipe instead: ```yaml jobs: @@ -239,44 +236,51 @@ jobs: The behavior matches the composite action exactly; only the install shape differs. Strategy is set via env (`SEMVERTAG_STRATEGY`) or CLI -flag (`--strategy …`). No outputs are produced in this shape — read +flag (`--strategy …`). This shape produces no outputs. Read the CLI stdout, or invoke `semvertag tag --json` and parse the envelope yourself. ## Troubleshooting -- **`Token rejected: 401. Verify SEMVERTAG_TOKEN is valid.`** — the - token is malformed, expired, or revoked. Verify in GitHub UI - (Settings → Developer settings → Personal access tokens) or - rotate the workflow secret. When using the composite action, - `GITHUB_TOKEN` is set automatically from the `token` input (which - defaults to `${{ github.token }}`). When using the pure-CLI recipe - in "Without the composite action", add - `env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}` to the run step. - -- **`Token missing scope or insufficient permission: 403`** — the - token lacks `contents: write` (fine-grained / workflow-scoped) or - `repo` / `public_repo` (classic). For workflow-scoped tokens, - add `permissions: contents: write` at the workflow level. For PATs, - re-issue with the right scope. - -- **`GitHub repo not found: repo='...'`** — `GITHUB_REPOSITORY` was - not exported, or `--repo OWNER/REPO` was not passed. Inside GHA, - `GITHUB_REPOSITORY` is auto-exported in every job; outside GHA, - set it explicitly. - -- **`Tag already exists: 'v...'`** — a previous run (or a concurrent - run) already created this tag. semvertag refuses to silently - succeed on a duplicate. Roll forward by pushing another commit - that changes the bump, or delete the duplicate tag. - -- **GitHub Enterprise, but the job connects to `api.github.com`** — - the default endpoint is `https://api.github.com`. Set - `SEMVERTAG_GITHUB__ENDPOINT` (note the double underscore) as a - workflow-level env pointing to the instance's API root, e.g. - `https://github.example.com/api/v3`. - -- **A bump-worthy push was never tagged** — the run for that push - failed or was skipped. Re-run it. Each run judges only the head - commit of its own push and does not look back, so the next push - cannot recover an earlier bump. +### `Token rejected: 401. Verify SEMVERTAG_TOKEN is valid.` + +The token is malformed, expired, or revoked. Verify it in the GitHub UI +(Settings → Developer settings → Personal access tokens) or rotate the +workflow secret. When using the composite action, `GITHUB_TOKEN` is set +automatically from the `token` input (which defaults to +`${{ github.token }}`). When using the pure-CLI recipe in "Without the +composite action", add `env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}` +to the run step. + +### `Token missing scope or insufficient permission: 403` + +The token lacks `contents: write` (fine-grained / workflow-scoped) or +`repo` / `public_repo` (classic). For workflow-scoped tokens, add +`permissions: contents: write` at the workflow level. For PATs, +re-issue with the right scope. + +### `GitHub repo not found: repo='...'` + +`GITHUB_REPOSITORY` was not exported, or `--repo OWNER/REPO` was not +passed. Inside GHA, `GITHUB_REPOSITORY` is auto-exported in every job; +outside GHA, set it explicitly. + +### `Tag already exists: 'v...'` + +A previous run (or a concurrent run) already created this tag. +semvertag refuses to silently succeed on a duplicate. Roll forward by +pushing another commit that changes the bump, or delete the duplicate +tag. + +### GitHub Enterprise, but the job connects to `api.github.com` + +The default endpoint is `https://api.github.com`. Set +`SEMVERTAG_GITHUB__ENDPOINT` (note the double underscore) as a +workflow-level env pointing to the instance's API root, e.g. +`https://github.example.com/api/v3`. + +### A bump-worthy push was never tagged + +The run for that push failed or was skipped. Re-run it. Each run judges +only the head commit of its own push and does not look back, so the +next push cannot recover an earlier bump. diff --git a/docs/providers/gitlab.md b/docs/providers/gitlab.md index 4d80041..25825bf 100644 --- a/docs/providers/gitlab.md +++ b/docs/providers/gitlab.md @@ -1,22 +1,21 @@ # GitLab CI Use semvertag in GitLab CI via a small inline job that installs `uv` -and runs `uvx semvertag tag`. No PyPI install in your repo, no -maintained pipeline YAML beyond the snippet below. +and runs `uvx semvertag tag`. Your repo needs no PyPI install and no +pipeline YAML beyond the snippet below. -> **Catalog component pending.** A one-line `include: - component: …` -> via the GitLab CI Catalog is the eventual delivery path — the -> descriptor lives at -> [`templates/semvertag.yml`](https://github.com/modern-python/semvertag/blob/main/templates/semvertag.yml) -> — but the component has not yet been published to gitlab.com's -> Catalog. Paste the job below into `.gitlab-ci.yml` until then. +> A one-line `include: - component: …` via the GitLab CI Catalog is the +> eventual delivery path, and its descriptor lives at +> [`templates/semvertag.yml`](https://github.com/modern-python/semvertag/blob/main/templates/semvertag.yml). +> The component has not yet been published to gitlab.com's Catalog. +> Paste the job below into `.gitlab-ci.yml` until then. -## Quick Start +## Quick start -The minimum useful pipeline: auto-tag on every push to the default +The minimum useful pipeline auto-tags on every push to the default branch. -> **Required setup.** Set `SEMVERTAG_TOKEN` as a project-level masked +> Set `SEMVERTAG_TOKEN` as a project-level masked > CI/CD variable holding a Project Access Token (or Personal Access > Token) with `api` + `write_repository` scope. `CI_JOB_TOKEN` works > on projects where the job-token write scope is opted in @@ -44,16 +43,15 @@ bump is warranted by the configured strategy, pushes a new tag to the project's `origin`. If no bump is warranted, the job exits 0 without pushing. -> **First tag.** semvertag bumps from the highest existing semver tag +> semvertag bumps from the highest existing semver tag > and never creates the first one. It reads only plain semver tags such > as `0.1.0`; a `v` prefix (`v0.1.0`) does not parse and is ignored. Until > one exists, every run reports `no_tags` and exits 0. Push one with > `git tag 0.1.0 && git push origin 0.1.0`. -> **Concurrency default.** `resource_group: semvertag` makes GitLab -> serialize concurrent `semvertag` jobs across pipelines on the same -> project — back-to-back pushes will queue rather than race the -> `create_tag` API. Drop or rename the group if you intentionally want +> `resource_group: semvertag` makes GitLab serialize concurrent +> `semvertag` jobs across pipelines on the same project, so +> back-to-back pushes queue instead of racing the `create_tag` API. Drop or rename the group if you intentionally want > concurrent tag pushes. ## Strategy @@ -73,7 +71,7 @@ block on the `include:`. The values and default match ## Required permissions -The job pushes a tag, so the token it uses MUST carry write access to +The job pushes a tag, so the token it uses must carry write access to the repository. semvertag reads the token from these env vars in order: `SEMVERTAG_GITLAB__TOKEN`, `SEMVERTAG_TOKEN`, `CI_JOB_TOKEN`, `GITLAB_TOKEN`. The first set value wins. @@ -82,13 +80,13 @@ vars in order: `SEMVERTAG_GITLAB__TOKEN`, `SEMVERTAG_TOKEN`, Two cases govern which token the job should use: -- **GitLab projects where the maintainer has opted in to job-token - write scope** (Settings → CI/CD → Token Permissions → *Allow access - from the project's token to write to the repository*). `CI_JOB_TOKEN` +- On GitLab projects where the maintainer has opted in to job-token + write scope (Settings → CI/CD → Token Permissions → *Allow access + from the project's token to write to the repository*), `CI_JOB_TOKEN` is auto-exported into every CI job and gets picked up by the alias - chain — no further configuration needed. -- **Projects that have NOT opted in**, or projects on older GitLab - versions where `CI_JOB_TOKEN` was scoped read-only by default. The + chain with no further configuration. +- On projects that have not opted in, or projects on older GitLab + versions where `CI_JOB_TOKEN` was scoped read-only by default, the consumer creates a Project Access Token (preferred; scoped to the one project) or a Personal Access Token (works but bleeds the user's scope across all their projects). Token scopes required: @@ -96,22 +94,23 @@ Two cases govern which token the job should use: variable named `SEMVERTAG_TOKEN`; the alias chain picks it up ahead of `CI_JOB_TOKEN`. -> **Masking caveat.** Because the alias chain reads -> `SEMVERTAG_GITLAB__TOKEN` → `SEMVERTAG_TOKEN` → `CI_JOB_TOKEN` → -> `GITLAB_TOKEN` in order and the first set value wins, a stale +> The alias chain reads `SEMVERTAG_GITLAB__TOKEN` → `SEMVERTAG_TOKEN` +> → `CI_JOB_TOKEN` → `GITLAB_TOKEN` in order and the first set value +> wins, so a stale > `SEMVERTAG_TOKEN` left over from a prior PAT-based setup will > silently override a freshly-rotated `CI_JOB_TOKEN`. If you migrate > from PAT → job-token, unset `SEMVERTAG_TOKEN` (or rotate its value > to empty) in the project's CI/CD variables. -**Self-hosted GitLab**: set `SEMVERTAG_GITLAB__ENDPOINT` (note the -double underscore — pydantic-settings uses `__` as the nested-key -delimiter, so `SEMVERTAG_GITLAB_ENDPOINT` with a single underscore is -silently ignored) as a project CI/CD variable pointing to the -instance's API root, e.g. `https://gitlab.example.com`. The default -is `https://gitlab.com` and is not auto-derived from `CI_SERVER_FQDN`. +For self-hosted GitLab, set `SEMVERTAG_GITLAB__ENDPOINT` as a project +CI/CD variable pointing to the instance's API root, e.g. +`https://gitlab.example.com`. The default is `https://gitlab.com` and +is not auto-derived from `CI_SERVER_FQDN`. Note the double underscore: +pydantic-settings uses `__` as the nested-key delimiter, so +`SEMVERTAG_GITLAB_ENDPOINT` with a single underscore is silently +ignored. -> **Endpoint shape.** Use scheme + host only. Do NOT append `/api/v4` +> Use scheme + host only for the endpoint. Do not append `/api/v4` > (the client adds it); a value like `https://gitlab.example.com/api/v4` > produces `…/api/v4/api/v4/…` URLs and 404s. A missing scheme > (`gitlab.example.com`) fails at request time with httpx @@ -158,25 +157,28 @@ semvertag: ## Troubleshooting -- **`Token missing scope or insufficient permission: 403`** — the - token does not have `api` + `write_repository` scope, or the - project's protected-tag rules disallow the bot from creating tags. - Verify the `SEMVERTAG_TOKEN` scopes in GitLab UI (Settings → Access - Tokens). - -- **`Project id missing. Set CI_PROJECT_ID or pass --project-id.`** — - the CI runner did not export `CI_PROJECT_ID` (the variable is - exported by every standard GitLab CI job; a custom executor that - strips CI variables would suppress it). Set `SEMVERTAG_PROJECT_ID` - as a project-level CI/CD variable as the override. - -- **Self-hosted GitLab, but the job connects to `gitlab.com`** - — the default endpoint is `https://gitlab.com` and is not - auto-derived from `CI_SERVER_FQDN`. Set - `SEMVERTAG_GITLAB__ENDPOINT` as a project-level CI/CD variable - pointing to the instance's API root. - -- **A bump-worthy push was never tagged** — the run for that push - failed or was skipped. Re-run it. Each run judges only the head - commit of its own push and does not look back, so the next push - cannot recover an earlier bump. +### `Token missing scope or insufficient permission: 403` + +The token does not have `api` + `write_repository` scope, or the +project's protected-tag rules disallow the bot from creating tags. +Verify the `SEMVERTAG_TOKEN` scopes in the GitLab UI (Settings → Access +Tokens). + +### `Project id missing. Set CI_PROJECT_ID or pass --project-id.` + +The CI runner did not export `CI_PROJECT_ID`. Every standard GitLab CI +job exports the variable, but a custom executor that strips CI +variables would suppress it. Set `SEMVERTAG_PROJECT_ID` as a +project-level CI/CD variable as the override. + +### Self-hosted GitLab, but the job connects to `gitlab.com` + +The default endpoint is `https://gitlab.com` and is not auto-derived +from `CI_SERVER_FQDN`. Set `SEMVERTAG_GITLAB__ENDPOINT` as a +project-level CI/CD variable pointing to the instance's API root. + +### A bump-worthy push was never tagged + +The run for that push failed or was skipped. Re-run it. Each run judges +only the head commit of its own push and does not look back, so the +next push cannot recover an earlier bump. diff --git a/docs/strategies/branch-prefix.md b/docs/strategies/branch-prefix.md index 5d3c2af..5ed1032 100644 --- a/docs/strategies/branch-prefix.md +++ b/docs/strategies/branch-prefix.md @@ -16,17 +16,18 @@ behavior. | anything else | none | A bump of `none` means the commit contributes nothing to the release -decision. Major bumps are not produced by `branch-prefix` — promote +decision. `branch-prefix` never produces a major bump. Promote to a new major version manually, or switch to -[Conventional Commits](conventional-commits.md) which recognizes +[Conventional Commits](conventional-commits.md), which recognizes `feat!` and `BREAKING CHANGE:`. ## Merge-commit detection -The strategy only fires on commits whose subject contains the literal -string `Merge branch` (the default `git merge` subject). Commits -without one of those marks return `none` regardless of prefix. This -means: +The strategy only fires on commits whose subject contains one of the +default merge marks: the literal string `Merge branch` (the default +`git merge` subject) or `Merge pull request` (GitHub's merge-commit +subject). Commits without one of those marks return `none` regardless +of prefix. For example: - Standard `git merge feature/foo` → subject `Merge branch 'feature/foo' into main` → bump = minor ✓ - GitHub's `Merge pull request #N from user/feature/foo` → bump = minor ✓ @@ -42,20 +43,20 @@ merge-commit conventions (e.g. squash-merge prefixes). The strategy reads its prefixes from the application's settings layer: -- `minor` — tuple of prefixes that trigger a minor bump (default +- `minor`: tuple of prefixes that trigger a minor bump (default `("feature/",)`). -- `patch` — tuple of prefixes that trigger a patch bump (default +- `patch`: tuple of prefixes that trigger a patch bump (default `("bugfix/", "hotfix/")`). -- `merge_mark_texts` — tuple of substrings that mark a subject as a +- `merge_mark_texts`: tuple of substrings that mark a subject as a merge commit (default `("Merge branch", "Merge pull request")`). -- `patch_on_non_merge_commit` — when `true`, a plain (non-merge) commit on +- `patch_on_non_merge_commit`: when `true`, a plain (non-merge) commit on the default branch bumps patch instead of producing no bump (default `false`). Set via `SEMVERTAG_BRANCH_PREFIX__PATCH_ON_NON_MERGE_COMMIT=true`. Affects only the non-merge case; a merge commit with an unrecognized prefix still produces no bump. These are set via the same pydantic-settings env-var mechanism used -for tokens / endpoints — see the provider docs for the variable +for tokens / endpoints. The provider docs describe the variable naming convention. ## Head commit only @@ -69,7 +70,7 @@ the earlier bump is not recovered. If your team commits Conventional Commits messages directly to the default branch (without merge commits), switch to -[Conventional Commits](conventional-commits.md) — that strategy +[Conventional Commits](conventional-commits.md). That strategy reads the head commit's subject and body and does not depend on merge metadata. @@ -78,5 +79,5 @@ merge metadata. The strategy is selected per project via the `strategy:` input on the relevant provider's component / action. See: -- [GitLab CI](../providers/gitlab.md) — set `strategy: branch-prefix` +- [GitLab CI](../providers/gitlab.md): set `strategy: branch-prefix` on the `include: - component:` block. diff --git a/docs/strategies/conventional-commits.md b/docs/strategies/conventional-commits.md index 024f59d..8d27e3f 100644 --- a/docs/strategies/conventional-commits.md +++ b/docs/strategies/conventional-commits.md @@ -19,7 +19,7 @@ commits since the latest tag; see [Head commit only](#head-commit-only). The grammar checked is `^(type)(?:\((scope)\))?(!)?:`. Anything not matching this pattern returns `none`. The `!` marker takes precedence -over the type — `chore!:` is a major bump even though `chore` is +over the type: `chore!:` is a major bump even though `chore` is otherwise unmapped. ## Customizing the type lists @@ -27,9 +27,9 @@ otherwise unmapped. The strategy reads its type lists from the application's settings layer: -- `minor_types` — tuple of types that trigger a minor bump (default +- `minor_types`: tuple of types that trigger a minor bump (default `("feat",)`). -- `patch_types` — tuple of types that trigger a patch bump (default +- `patch_types`: tuple of types that trigger a patch bump (default `("fix", "perf")`). Both lists are validated against the lowercase-letters-only regex @@ -58,13 +58,13 @@ earlier bump is not recovered. If your team merges via short-lived prefixed branches (`feature/...`, `bugfix/...`) and does not enforce Conventional Commits on each -commit, switch to [Branch prefix](branch-prefix.md) — it reads the -merge commit's source branch rather than the per-commit subject. +commit, switch to [Branch prefix](branch-prefix.md), which reads the +merge commit's source branch instead of the per-commit subject. ## Consumer integration The strategy is selected per project via the `strategy:` input on the relevant provider's component / action. See: -- [GitLab CI](../providers/gitlab.md) — set +- [GitLab CI](../providers/gitlab.md): set `strategy: conventional-commits` on the `include: - component:` block. diff --git a/mkdocs.yml b/mkdocs.yml index 5b7ead7..b2d8ee3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,7 +4,7 @@ repo_url: https://github.com/modern-python/semvertag docs_dir: docs edit_uri: edit/main/docs/ nav: - - Quick Start: index.md + - Quick start: index.md - Providers: - GitLab CI: providers/gitlab.md - GitHub Actions: providers/github.md