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.

Read a run and its log from the CLI

92d8afc86ab7a5e8f2e56a8981e0614aa55980f1 · Keenan Nemetz · 4h ago · parent 4b8c9e1d

Read a run and its log from the CLI

The queue and its logs live on the instance, so the CLI now has read verbs
beside the cancel and rerun writes: run list, run show and run logs. They
check the repository's read capability, scope an id to its repository, and
stream a log to stdout with refusals on stderr.

change show lists the runs for the head and, when the landing needs one, the
combined result, so a red check carries the id needed to open it. The queue
search narrows by status, branch and commit prefix without a pager.

closes: 01M6YZAQZVJWTA9BA5RV2H6PQ8

cmd/vole/run_test.go +37

@@ -0,0 +1,37 @@
1 package main
2
3 import (
4 "strings"
5 "testing"
6 "time"
7
8 "rodent.software/vole/client"
9 )
10
11 // TestFormatRunSummary exposes the fields a script needs to find the run that
12 // failed, including the full id that `run show` and `run logs` take.
13 func TestFormatRunSummary(t *testing.T) {
14 at := time.Date(2026, 11, 9, 12, 0, 0, 0, time.UTC)
15 r := client.RunSummary{
16 ID: "01M7143PYHX6NSF1J92JT15EQX", Status: "failed",
17 Workflow: ".vole/workflows/ci.yaml", Job: "build", Branch: "main",
18 SHA: strings.Repeat("ab", 20), Trigger: "push", TriggerTitle: "pushed to main",
19 ExitCode: 1, Attempts: 2, HasLog: true, Created: at,
20 }
21 out := formatRunSummary("%(shortid) %(status) %(workflow)/%(job) %(branch) %(exit) %(attempts) %(log) %(created)", r)
22 want := "01M7143P failed .vole/workflows/ci.yaml/build main 1 2 yes 2026-11-09T12:00:00Z"
23 if out != want {
24 t.Fatalf("format = %q, want %q", out, want)
25 }
26 }
27
28 func TestRunIndicesMustBeNumbers(t *testing.T) {
29 if n, err := positiveOrZero("", "--attempt"); err != nil || n != 0 {
30 t.Fatalf("omitted = %d, %v; want the default", n, err)
31 }
32 for _, raw := range []string{"-1", "two"} {
33 if _, err := positiveOrZero(raw, "--attempt"); err == nil || !isUsageErr(err) {
34 t.Fatalf("%q was accepted", raw)
35 }
36 }
37 }