Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Phase 7 slice 8: a tag push builds the release

0f442b1c87ca3920f770531c9ad97277d0231f51 · Keenan Nemetz · 17d ago · parent 88e980b0

Phase 7 slice 8: a tag push builds the release

on.push.tags opens CI to tag pushes; release: true on an artifact publishes it to
that tag's release rather than only to the run. The runner commits the same
pointers `vole release attach` writes, so the release page, the ETags, the
immutability rules and the sweep needed no changes: CI gained a trigger, not a
publish path.

Three rules, each from something that went wrong in the first draft:

- a workflow that says tags: runs ONLY on tags. Saying what you trigger on is
  saying it; the first version defaulted to every branch too and queued a doomed
  release build on every commit
- a tag run never counts as a branch's required check. A tag on a branch tip
  produces a second run for the same sha, and ForSHA would have let a release
  build satisfy — or break — the gate for the commit it tags. It now filters to
  refs/heads/%
- a lightweight tag cannot name a release: the build runs and the publish fails
  saying so, because a tag with no message has no notes and no tagger. The server
  resolves annotated-ness at enqueue (the tag object's type) rather than having
  the runner parse ls-remote peeled lines

Tag and annotated-ness ride the run and the job payload beside the release
artifact patterns, pinned at enqueue like everything else.

Measured through the binary: a branch push queued nothing, a tag push queued the
release job, the runner published dist/tool-linux, the release page listed it
with the tag marked annotated, the download returned the bytes, and git log vole
read "release: v1.0.0 artifacts from run 01M5MSX3...".

internal/workflow/workflow_test.go +83

@@ -213,3 +213,86 @@ jobs:
213 213 t.Fatal("two jobs publishing the site were accepted")
214 214 }
215 215 }
216
217 // TestTagTriggers are the rules a release build depends on: tags have no default,
218 // and saying "tags:" means you said what the workflow triggers on.
219 func TestTagTriggers(t *testing.T) {
220 // Tag-only: no branch triggers, because the workflow said what it runs on.
221 tagOnly, problems := Parse("ci.yaml", `version: 1
222 on:
223 push:
224 tags: ['v*']
225 jobs:
226 release:
227 steps:
228 - run: make dist
229 artifacts:
230 - path: dist/*
231 release: true
232 `)
233 if len(problems) > 0 {
234 t.Fatalf("a tag-only workflow was refused: %v", problems)
235 }
236 wf := tagOnly
237 if len(wf.Branches) != 0 {
238 t.Fatalf("tag-only workflow also runs on branches: %v", wf.Branches)
239 }
240 if !wf.AppliesToTag("v1.0.0") || wf.AppliesToTag("nightly") {
241 t.Fatalf("tag matching is wrong for %v", wf.Tags)
242 }
243 if !wf.Jobs[0].Artifacts[0].Release {
244 t.Fatal("the release flag did not survive parsing")
245 }
246
247 // Neither list says nothing, so the default is every branch — unchanged behaviour.
248 plain, problems := Parse("ci.yaml", `version: 1
249 jobs:
250 test:
251 steps:
252 - run: true
253 `)
254 if len(problems) > 0 || len(plain.Branches) != 1 || plain.Branches[0] != "*" {
255 t.Fatalf("a workflow with no triggers = %+v %v", plain, problems)
256 }
257
258 // Release artifacts on a branch workflow have no tag to attach to.
259 if _, problems := Parse("ci.yaml", `version: 1
260 jobs:
261 build:
262 steps:
263 - run: true
264 artifacts:
265 - path: dist/*
266 release: true
267 `); len(problems) == 0 {
268 t.Fatal("release artifacts were accepted without a tag trigger")
269 }
270
271 // An explicit empty branch list means branches too, alongside tags.
272 both, problems := Parse("ci.yaml", `version: 1
273 on:
274 push:
275 branches: []
276 tags: ['v*']
277 jobs:
278 build:
279 steps:
280 - run: true
281 `)
282 if len(problems) > 0 || len(both.Branches) != 0 || len(both.Tags) != 1 {
283 t.Fatalf("branches: [] with tags = %+v %v", both, problems)
284 }
285
286 // A tag pattern that is not a pattern is refused like a branch one.
287 if _, problems := Parse("ci.yaml", `version: 1
288 on:
289 push:
290 tags: ['../x']
291 jobs:
292 build:
293 steps:
294 - run: true
295 `); len(problems) == 0 {
296 t.Fatal("a tag pattern with .. was accepted")
297 }
298 }