vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 19m ago
4b8c9e1d
| 1 | package ssh |
| 2 | |
| 3 | // `vole change show` asks the instance whether the landing tree has been tested. |
| 4 | // A clone can see the commits and the refs, but not the approvals or the run |
| 5 | // results that decide whether a landing is allowed, so the answer comes from the |
| 6 | // same plan the merge button and the push hook use. |
| 7 | |
| 8 | import ( |
| 9 | "fmt" |
| 10 | "strings" |
| 11 | "time" |
| 12 | |
| 13 | xssh "golang.org/x/crypto/ssh" |
| 14 | |
| 15 | "rodent.software/vole/ci/runs" |
| 16 | "rodent.software/vole/forge/access" |
| 17 | "rodent.software/vole/forge/landing" |
| 18 | "rodent.software/vole/internal/format" |
| 19 | "rodent.software/vole/metadata" |
| 20 | ) |
| 21 | |
| 22 | func (s *Server) serveChangeStatus(ch xssh.Channel, argv []string, user, remote string) int { |
| 23 | if len(argv) != 3 { |
| 24 | fmt.Fprint(ch, "vole: usage: vole-change-status <repository> <request-id>\n") |
| 25 | return 129 |
| 26 | } |
| 27 | name, ok := repoName(argv[1]) |
| 28 | if !ok { |
| 29 | fmt.Fprint(ch, "vole: invalid repository name\n") |
| 30 | return 129 |
| 31 | } |
| 32 | id := strings.TrimSpace(argv[2]) |
| 33 | m, err := s.cfg.Repos.Open(name) |
| 34 | if err != nil { |
| 35 | fmt.Fprintf(ch, "vole: repository %q not found\n", name) |
| 36 | return 1 |
| 37 | } |
| 38 | a, err := m.ACL() |
| 39 | if err != nil { |
| 40 | fmt.Fprintf(ch, "vole: cannot read access rules for %s\n", name) |
| 41 | return 1 |
| 42 | } |
| 43 | role := a.EffectiveRole(user, s.cfg.Identity != nil && s.cfg.Identity.IsAdmin(user)) |
| 44 | if !role.AtLeast(access.Read) { |
| 45 | fmt.Fprintf(ch, "vole: %s may not read change requests in %s\n", user, name) |
| 46 | return 1 |
| 47 | } |
| 48 | req, err := metadata.GetRequest(m.Git(), id) |
| 49 | if err != nil { |
| 50 | fmt.Fprintf(ch, "vole: %v\n", err) |
| 51 | return 1 |
| 52 | } |
| 53 | if !req.Open() { |
| 54 | fmt.Fprintf(ch, "combined: not needed (%s)\n", req.State) |
| 55 | return 0 |
| 56 | } |
| 57 | gate := landing.Gate{ |
| 58 | Repo: m.Git(), Name: m.Name, ACL: a, |
| 59 | Actor: user, Role: role, |
| 60 | Runs: func() (*runs.Store, error) { return s.cfg.Runs, nil }, |
| 61 | } |
| 62 | plan, err := landing.Prepare(gate, req.ID, user, time.Now().UTC()) |
| 63 | if err != nil { |
| 64 | fmt.Fprintf(ch, "combined: cannot tell (%v)\n", err) |
| 65 | return 0 |
| 66 | } |
| 67 | switch { |
| 68 | case len(plan.Conflicts) > 0: |
| 69 | fmt.Fprintf(ch, "combined: conflict with %s in %s\n", plan.Base, strings.Join(plan.Conflicts, ", ")) |
| 70 | case !plan.MergeCommit: |
| 71 | fmt.Fprint(ch, "combined: not needed (fast-forward)\n") |
| 72 | case plan.Ready: |
| 73 | fmt.Fprintf(ch, "combined: passed %s\n", format.SHA(plan.NewTip)) |
| 74 | case !plan.Checks: |
| 75 | fmt.Fprintf(ch, "combined: no checks required %s\n", format.SHA(plan.NewTip)) |
| 76 | case len(plan.Gaps) == 0: |
| 77 | fmt.Fprintf(ch, "combined: untested %s\n", format.SHA(plan.NewTip)) |
| 78 | case plan.Pending: |
| 79 | fmt.Fprintf(ch, "combined: testing %s\n", format.SHA(plan.NewTip)) |
| 80 | default: |
| 81 | fmt.Fprintf(ch, "combined: failed %s\n", format.SHA(plan.NewTip)) |
| 82 | } |
| 83 | if plan.Blocked != "" { |
| 84 | fmt.Fprintf(ch, "landing: blocked (%s)\n", plan.Blocked) |
| 85 | } else if plan.Ready { |
| 86 | fmt.Fprint(ch, "landing: ready\n") |
| 87 | } else if len(plan.Gaps) > 0 { |
| 88 | fmt.Fprint(ch, "landing: waiting for the combined result\n") |
| 89 | } |
| 90 | return 0 |
| 91 | } |
| 92 |