Commit 3bcdce33fb

3bcdce33fb9a2309312854331359d376171c7368

parent: bb4c0aeb12

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 08:26 UTC

web: step fold shows the step's first line; changelog: deploy order

Ref #266

Layout: unified · split

CHANGELOG.org +4 −1
@@ -144,7 +144,10 @@ for the eighteen commands whose CLI path differs from the registry's
144 a scratch repository per the CI page. (#260) 144 a scratch repository per the CI page. (#260)
145- =build show= names a failed build's step and duration; =build log 145- =build show= names a failed build's step and duration; =build log
146 --step <n>|failed --tail <lines>= reads one step's output or the 146 --step <n>|failed --tail <lines>= reads one step's output or the
147 last lines of the stored log. (#266) 147 last lines of the stored log. Deploy gitbayd before the runner: an
148 older server refuses the runner's =--step= and =--reason= (exit 2),
149 and its failed builds stay running until the reaper fails them.
150 (#266)
148 151
149* v1.36.0 — 2026-09-23 152* v1.36.0 — 2026-09-23
150 153
internal/httpd/buildpages_test.go +24
@@ -230,3 +230,27 @@ func TestBuildPageFoldsStepsAndOpensFailure(t *testing.T) {
230 t.Errorf("%d failed anchors, want 1", n) 230 t.Errorf("%d failed anchors, want 1", n)
231 } 231 }
232} 232}
233
234// A multi-line step's fold summary shows only its first line, as build
235// show does; the whole step is in the log (#266).
236func TestBuildPageStepSummaryIsFirstLine(t *testing.T) {
237 step := "set -e\ngo test ./..."
238 b := control.BuildOut{Number: 62, Job: "test", Status: "failure",
239 SHA: "ff6271a9d4570cd46f169091637a9d2e40ad5c2b", Ref: "main",
240 CreatedAt: "2026-08-28T04:42:54Z", FinishedAt: "2026-08-28T04:43:54Z", DurationS: 60,
241 Steps: []string{step}, FailedStep: 1, FailedReason: "exit 1"}
242 log := "$ git clone x (ff6271a9d4)\n$ " + step + "\n--- FAIL: TestCLI\nstep 1/1 failed: exit 1\n"
243 v := buildView{repoPage: testRepoPage(), Build: b, Log: log, Duration: "1m0s"}
244 v.Steps, v.Failed = logSteps(log, b)
245 var sb strings.Builder
246 if err := web.Render(&sb, "build.html", v); err != nil {
247 t.Fatalf("render: %v", err)
248 }
249 out := sb.String()
250 if !strings.Contains(out, "<code>set -e</code>") {
251 t.Errorf("summary does not show the step's first line:\n%s", out)
252 }
253 if strings.Contains(out, "<code>set -e\ngo test") {
254 t.Errorf("summary shows the whole multi-line step")
255 }
256}
internal/httpd/builds.go +5 −1
@@ -306,6 +306,9 @@ type buildView struct {
306 306
307type logStep struct { 307type logStep struct {
308 control.LogSection 308 control.LogSection
309 // Head is the step's first line, which its fold's summary shows, as
310 // build show does; the full step is in the log below it.
311 Head string
309 Failed bool 312 Failed bool
310} 313}
311 314
@@ -326,7 +329,8 @@ func logSteps(log string, b control.BuildOut) ([]logStep, bool) {
326 failed := control.FailedSection(sections, b.Status, b.FailedStep) 329 failed := control.FailedSection(sections, b.Status, b.FailedStep)
327 out := make([]logStep, len(sections)) 330 out := make([]logStep, len(sections))
328 for i, s := range sections { 331 for i, s := range sections {
329 out[i] = logStep{LogSection: s, Failed: i == failed} 332 head, _, _ := strings.Cut(s.Step, "\n")
333 out[i] = logStep{LogSection: s, Head: head, Failed: i == failed}
330 } 334 }
331 return out, failed >= 0 335 return out, failed >= 0
332} 336}
internal/web/templates/build.html +1 −1
@@ -16,7 +16,7 @@
16<pre class="code buildlog" tabindex="0">{{.Log}}</pre> 16<pre class="code buildlog" tabindex="0">{{.Log}}</pre>
17{{else if .Steps}}{{$total := len .Build.Steps}}{{range .Steps}} 17{{else if .Steps}}{{$total := len .Build.Steps}}{{range .Steps}}
18<details class="difffold buildstep"{{if .Failed}} id="failed" open{{end}}> 18<details class="difffold buildstep"{{if .Failed}} id="failed" open{{end}}>
19 <summary>{{if .N}}<span>step {{.N}}/{{$total}}</span> <code>{{.Step}}</code>{{else}}<span>setup</span>{{end}}{{if .Failed}} <span class="chip check-failure">failed</span>{{end}}</summary> 19 <summary>{{if .N}}<span>step {{.N}}/{{$total}}</span> <code>{{.Head}}</code>{{else}}<span>setup</span>{{end}}{{if .Failed}} <span class="chip check-failure">failed</span>{{end}}</summary>
20 <pre class="code buildlog" tabindex="0">{{.Text}}</pre> 20 <pre class="code buildlog" tabindex="0">{{.Text}}</pre>
21</details>{{end}} 21</details>{{end}}
22{{else if .Log}}<pre class="code buildlog" tabindex="0">{{.Log}}</pre>{{else}}<p class="empty-note">no log yet</p>{{end}} 22{{else if .Log}}<pre class="code buildlog" tabindex="0">{{.Log}}</pre>{{else}}<p class="empty-note">no log yet</p>{{end}}