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 | } | ||