Migrate to labelled pull requests¶
PullRequestDraft and PullRequest gained a Labels field. If you never set
labels, nothing about your code changes and you can stop reading.
If you do set them, one thing changes and it is sharp.
Create can return a pull request and an error¶
When a label could not be applied, Create returns the created pull request
together with an error wrapping ErrNotHonoured. The pull request exists. The
error says a label did not land.
So this, which was correct before, is now a bug:
pr, err := prs.Create(ctx, owner, repo, draft)
if err != nil {
return err // treats a successful create as a failure
}
Create makes no at-most-once claim, so the retry that usually follows a
returned error opens a second pull request.
Write this instead:
pr, err := prs.Create(ctx, owner, repo, draft)
if err != nil && !errors.Is(err, forge.ErrNotHonoured) {
return err // a real failure; nothing was created
}
// pr exists either way.
if err != nil {
// A label was dropped. errors.Hints names which.
log.Warn("pull request created without every label", "err", err)
}
This is the same shape ReleasePublisher.CreateRelease already uses, and the
same two guarantees hold: the returned value is populated whenever the thing
exists, and ErrNotHonoured never travels alone. If your codebase calls both,
check they agree — two call sites disagreeing about one sentinel is the defect
this shape produces.
Every label must already exist¶
A name that matches no label on the repository is not created. It is not
sent, and it comes back in the ErrNotHonoured hint.
Matching is exact. Release::Pending does not resolve to a release::pending
label.
GitLab and GitHub would both create an unknown name on the spot, and Gitea cannot, because its API takes label IDs rather than names. Rather than guarantee something one adapter provably cannot honour, the contract declines everywhere — a guarantee that holds on three forges of four is worse than none, because it reads as safe and only fails where nobody tested.
Seed your labels first. On GitLab a group label is inherited by every subproject and appears in the project's own label listing, so one group label covers the whole group with no per-project setup. GitHub has no organisation-level inheritance, so seeding is per-repository.
Labels cannot be changed afterwards¶
There is no AddLabels, no SetLabels, no RemoveLabels, and Update does
not touch them. Labels is set once, at creation.
Mutating a label is how tooling encodes state on a pull request, and a caller
that reads a forge record as evidence of what landed is the failure
ResolveMergedCommit exists to prevent. If you genuinely need it, reach your
platform's own client through ProviderUnwrapper. That is deliberately the
awkward path.
Reference¶
- Spec 0014, decisions D2, D3, D5 and D7.
- Provider matrix for which forge supports what.