Skip to content

colophon apply

Description

Carry what a commit asserted to the forge object it names

Read the commits a push added, take the Updates: trailers they carry, and apply each to the issue, merge request or wiki page it names. This is the one verb that runs when a merge lands rather than when a release is cut, so a record outside the repository can be told what became of the work.

Nothing is inferred. A commit says what happened to a record or the record is left alone, for the reason a Release-As: trailer exists: a tool cannot know which merge finished a piece of work, and a record it marks done too early is worse than one left stale, because a wrong record is trusted.

Usage

colophon apply --from <commit> --to <commit> [flags]

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

Flags

Flag Description Default
--from Commit the push started from. Empty, or the all-zero SHA a forge sends for a branch's first push, means the tip alone. empty
--to Commit the push ended at HEAD
--remote Git remote to read 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 .

The span

--from and --to are the commits a push added, which the CI environment knows: on GitLab they are CI_COMMIT_BEFORE_SHA and CI_COMMIT_SHA.

This is deliberately not the span the release verbs use. plan and propose ask what has landed since the last release; a run that acts on what a person wrote must see exactly what this push carried, or a repository releasing weekly would act on the whole week again on every merge.

What it writes

Target What happens
issue a comment
merge request a comment
wiki page one frontmatter field is set; nothing else on the page is touched

The syntax of the trailer, its targets and its keywords are in Commit trailers and annotations.

A wiki page whose frontmatter does not carry the field a keyword names is refused rather than grown one, and a page already carrying the value is reported as unchanged and not rewritten.

Running it twice

Safe. Each comment is posted under an idempotency key naming the commit that asserted it and the object it was about, and the forge provider returns the existing comment rather than posting a second. A later commit saying the same thing about the same object is a second comment, because it is a second assertion.

The key defends against a retry, not against two runs racing: it is not atomic, and two concurrent pushes asserting the same thing can both post.

What each forge can do

Every capability is optional and separately absent. An instruction the forge cannot carry is reported and skipped; the rest still apply.

Forge Issue comment Merge request comment Wiki
GitLab yes yes yes
GitHub yes yes yes
Gitea, Codeberg yes yes yes
Bitbucket no yes no

Checked at go/forge v0.32.0 with forge-gitlab v0.25.0, forge-github v0.26.0, forge-gitea v0.26.0 and forge-bitbucket v0.17.2. Bitbucket Cloud has no issue tracker and no wiki API.

Output

One line per instruction, then a summary when anything wants attention:

  applied   #13
  applied   /wikis/specs/0079-pipeline-churn — status set to IMPLEMENTED
  unchanged phpboyscout/cicd/wikis/specs/0080-other — status is already IMPLEMENTED
  skipped   !45 — this forge has no comment capability for merge request
  failed    phpboyscout/go/forge#23 — 403 forbidden

2 of 5 needed attention. The merge stands; nothing here failed the pipeline.

Exit code

Non-zero when an instruction failed, and zero otherwise.

Outcome Exit
every instruction applied or already present 0
an instruction was skipped — this forge has no such capability 0
an instruction failed — the forge was asked and refused 1
the run could not start — unreadable manifest, unknown forge, bad span 1

A write that lands but loses part of what was asked is applied, not failed. GitLab's wiki API takes no commit message, so a page edit there succeeds with GitLab's own message instead of colophon's; the line says the page changed and names what was dropped, and the exit is 0.

A failure does not stop the instructions after it. The run carries everything it can, prints one line per instruction, and then reports the failure. By the time this runs the merge has happened, so abandoning the rest would help nobody.

A skipped instruction is never a failure. It means the forge cannot do this at all — Bitbucket has no wiki, Gitea's adapter has no comment capability — so no change to your repository could ever clear it, and gating on it would report a problem forever.

Choosing whether that fails your pipeline

That choice is allow_failure, and it belongs in your CI configuration rather than in colophon:

colophon-apply:
  allow_failure: true    # the job goes yellow; the pipeline stays green

Yellow means the run completed and something wants an engineer's eye, which is usually what you want here: the merge already happened, so a red default branch gives nobody anything to go back and fix. Set allow_failure: false where an unapplied instruction should stop the line.

This changed. apply used to exit zero whatever happened, so a job whose only instruction failed went green and nothing said so. Colophon now reports the outcome and leaves the pipeline's colour to allow_failure. A repository running apply without allow_failure will see a red job where it saw a green one; allow_failure: true restores the old colour and keeps the signal.