Skip to content

colophon release

Description

Create the release for a tag that already exists

Create the release object for an existing tag, whether publish --tag-only or a person made it. The tag is resolved on the remote and must be contained by the target branch; a tag on a commit that never landed is refused, and there is no flag past that. The notes come from CHANGELOG.md at the tagged commit when it names the version, and are rendered from the commits since the previous release when it does not.

Usage

colophon release --tag <tag> [flags]

Run colophon release --help for the authoritative, always-current flag set.

Flags

Flag Description Default
--tag The existing tag to release, e.g. v1.2.3. Required. none
--target Branch that must contain the tagged commit main
--remote Git remote to read from origin
--forge Forge the remote is on when its host does not say: gitlab, github or gitea. Same as forge in .colophon.yaml; both must agree when given. empty
-C, --repo Path to the git repository .
--assets goreleaser dist directory; its artifacts.json names what the release should carry none
--asset-base URL the artefacts were uploaded under, appended with each asset's name none
--announce-failure-exit Exit with this code when the release stood and an announcement failed. 0 keeps exit 0; 1, 2 and anything above 125 are refused. See making a failed announcement show 0
--no-announce Create the release without announcing it; the adapters .colophon.yaml names stay quiet false

Attaching assets

--assets and --asset-base attach what a build uploaded. --assets names goreleaser's dist directory, whose artifacts.json says what the release carries; a project that builds some other way names its files in .colophon.yaml instead (release manifest) and passes only --asset-base. One source or the other, never both.

--asset-base must match wherever the build tool actually uploaded. colophon does not derive it, and every asset is checked to resolve before anything is published: a forge records an asset's location without fetching it, so a release can otherwise be published complete and correct with every link returning 404. A dist with no base is refused rather than half-honoured.

The check waits out a store that is not readable yet. An object uploaded seconds before can answer 404 at a CDN edge, so a 404, a timeout or a server error is retried for about a minute before the release is refused. A refusal (401, 403, 410) fails at once.

The release it created is announced

As publish: a release this run created is announced through the adapters .colophon.yaml names, under an announce <tag> heading in the report; one already there is not. A commit an adapter makes is attributed to colophon <[email protected]>, since this verb carries no author flags.

Where the notes come from

release reads CHANGELOG.md at the tagged commit. When its newest section names the tag's version, that section is the release's notes, byte for byte what publish would have used. That is every release publish --tag-only made.

When it does not, which is what a hand-cut tag looks like, the notes are rendered from the commits between the previous release and the tag, the way propose would have rendered them, and the output says so:

v0.1.4
  commit   3c1f0a92
  warning  CHANGELOG.md at 3c1f0a92 names v0.1.3, not v0.1.4; notes rendered from history and the file is not backfilled
  release  created

The file is not backfilled. The release object carries those notes and nothing else does; the next propose renders only the version it proposes. See Recover with a hand-cut tag.

The previous release is the highest semver tag reachable from the tagged commit whose version is below the one being released. Two tags below it that tie on precedence and differ only in build metadata are refused rather than chosen between, for the same reason plan refuses them.

What is refused

  • A tag the remote does not carry. release never creates one.
  • A tag whose commit the target branch does not contain. Name the branch that does contain it with --target; there is no flag that skips the check.
  • A tag that is not a semantic version.
  • A tag with a directory prefix, such as lint/v0.6.0. It carries no release object; the error names the root tag to release instead.
  • --assets without --asset-base, or the reverse.

A hold in .colophon.yaml is not consulted. A hold stops publish deciding to release; once a tag exists the decision has been made.

Re-running

Safe, and it finishes what it finds. An existing release is reported as already present, and when --assets names artefacts the release does not carry, they are attached to it by name and counted:

  release  already present
  assets   2 attached to the existing release
  extra    the release also carries README.pdf, which the manifest does not name

Names already on the release are left exactly as they are, whatever they point at. Names the release carries and the manifest does not are reported and never removed. A second run attaches nothing. On a forge that cannot hold a hosted location as an asset, nothing is attached and the output says so, as on first creation.

A draft an interrupted run left behind is finished by the next run. On GitHub and Gitea a release is created as a draft and published last, so a job killed between the two leaves a release nobody can see. release publishes before it creates anything, so the next run finishes that one rather than adding a second beside it, and says which happened:

  release  finished a draft an interrupted run left behind

The order matters and is not an implementation detail: a stranded draft is invisible to a read, and GitHub permits several releases on one tag, so a run that created first would succeed and leave two. GitLab models no draft and is untouched by any of this.

Where a tag already carries more than one release, the run is refused with the candidates named, and nothing is created. Choosing between them is a person's, and there is no delete in the forge contract to undo a wrong guess.

In the pipeline

This is the job the cicd component's colophon-release runs on the tag pipeline, after goreleaser has uploaded, so a release is never visible without its assets. Until the component invokes release directly it runs publish with the same asset flags, which creates the same release for a tag publish made and no release for one it did not.