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 · 3h 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
server/ssh/status.go +41
| @@ -7,6 +7,8 @@ package ssh | |||
| 7 | 7 | ||
| 8 | 8 | import ( | |
| 9 | 9 | "fmt" | |
| 10 | "io" | ||
| 11 | "sort" | ||
| 10 | 12 | "strings" | |
| 11 | 13 | "time" | |
| 12 | 14 | ||
| @@ -50,6 +52,10 @@ func (s *Server) serveChangeStatus(ch xssh.Channel, argv []string, user, remote | |||
| 50 | 52 | fmt.Fprintf(ch, "vole: %v\n", err) | |
| 51 | 53 | return 1 | |
| 52 | 54 | } | |
| 55 | // The runs for the head are the ones a reviewer opens first, and they are true | ||
| 56 | // whether the change is open or already landed. The run id is the whole point: | ||
| 57 | // without it `vole run show` and `vole run logs` have nothing to take. | ||
| 58 | writeRunLines(ch, s.cfg.Runs, name, req.Head, "head") | ||
| 53 | 59 | if !req.Open() { | |
| 54 | 60 | fmt.Fprintf(ch, "combined: not needed (%s)\n", req.State) | |
| 55 | 61 | return 0 | |
| @@ -87,5 +93,40 @@ func (s *Server) serveChangeStatus(ch xssh.Channel, argv []string, user, remote | |||
| 87 | 93 | } else if len(plan.Gaps) > 0 { | |
| 88 | 94 | fmt.Fprint(ch, "landing: waiting for the combined result\n") | |
| 89 | 95 | } | |
| 96 | // The combined result is a different commit once the base has moved, so its | ||
| 97 | // runs are not the head's. They are the ones that explain a failed combined | ||
| 98 | // line, and a fast-forward has no separate commit to ask about. | ||
| 99 | if plan.NewTip != "" && plan.NewTip != req.Head { | ||
| 100 | writeRunLines(ch, s.cfg.Runs, name, plan.NewTip, "combined") | ||
| 101 | } | ||
| 90 | 102 | return 0 | |
| 91 | 103 | } | |
| 104 | |||
| 105 | // writeRunLines lists the newest run per job for one commit, in the same order the | ||
| 106 | // change page's Runs tab uses. It is display text, not a wire format: the id is what | ||
| 107 | // a reader carries to `vole run show`, and the status says whether that is the run to | ||
| 108 | // open. A missing queue is not an error here; the rest of the status still answers. | ||
| 109 | func writeRunLines(out io.Writer, store *runs.Store, repo, sha, where string) { | ||
| 110 | if store == nil || sha == "" { | ||
| 111 | return | ||
| 112 | } | ||
| 113 | byKey, err := store.ForSHA(repo, sha) | ||
| 114 | if err != nil { | ||
| 115 | return | ||
| 116 | } | ||
| 117 | keys := make([]string, 0, len(byKey)) | ||
| 118 | for key := range byKey { | ||
| 119 | keys = append(keys, key) | ||
| 120 | } | ||
| 121 | sort.Strings(keys) | ||
| 122 | for _, key := range keys { | ||
| 123 | r := byKey[key] | ||
| 124 | exit := "" | ||
| 125 | switch r.Status { | ||
| 126 | case runs.StatusSuccess, runs.StatusFailed, runs.StatusCanceled, runs.StatusError: | ||
| 127 | exit = fmt.Sprintf(" exit %d", r.ExitCode) | ||
| 128 | } | ||
| 129 | fmt.Fprintf(out, "run: %s %s %s/%s%s (%s)\n", | ||
| 130 | r.ID, r.Status, r.Workflow, r.Job, exit, where) | ||
| 131 | } | ||
| 132 | } | ||