Commit 7f779fb152
7f779fb1526af0c7756ea946c8f98b4c983cc771
parent: 8e529af22f
Verified · cmc ci/build: success ci/test: failure
cmc <hello@cleberg.net> · 2026-10-02 04:42 UTC
control: fingerprints fit narrow terminals; e2e reads repo list as a screen
A key fingerprint on a screen is a dim flex cell, cut to fit like a
title; repo commit's signature field is Signed, so its URL line fits 60
columns. e2e's terminal tests recognise repo list by its section
heading.
Ref #319
Layout: unified · split
e2e/term_test.go
+3 −3
| @@ -25,7 +25,7 @@ func TestTermEnvSelectsTerminalOutput(t *testing.T) { |
| 25 | 25 | t.Errorf("plain repo list: %q", plain) |
| 26 | 26 | } |
| 27 | 27 | term, _, _ := inst.sshTerm(t, key, "80,color", "repo", "list") |
| 28 | | if !strings.HasPrefix(term, "\x1b[2malice/app\x1b[0m ") { |
| 28 | if !strings.HasPrefix(term, "\x1b[1m\x1b[34mRepositories (1)") { |
| 29 | 29 | t.Errorf("terminal repo list: %q", term) |
| 30 | 30 | } |
| 31 | 31 | } |
| @@ -75,10 +75,10 @@ func TestTermEnvOverMultiplexedSession(t *testing.T) { |
| 75 | 75 | exec.Command("ssh", "-o", "ControlPath="+sock, "-O", "exit", "git@127.0.0.1").Run() |
| 76 | 76 | }) |
| 77 | 77 | |
| 78 | | if out := mux("80"); !strings.HasPrefix(out, "alice/app ") { |
| 78 | if out := mux("80"); !strings.HasPrefix(out, "Repositories (1)\nalice/app public") { |
| 79 | 79 | t.Fatalf("master session: %q", out) |
| 80 | 80 | } |
| 81 | | if out := mux("80,color"); !strings.HasPrefix(out, "\x1b[2malice/app") { |
| 81 | if out := mux("80,color"); !strings.HasPrefix(out, "\x1b[1m\x1b[34mRepositories") { |
| 82 | 82 | t.Errorf("second session kept the master's GITBAY_TERM: %q", out) |
| 83 | 83 | } |
| 84 | 84 | if out := mux(""); !strings.HasPrefix(out, "alice/app\t") { |
internal/control/deploykey.go
+1 −1
| @@ -129,7 +129,7 @@ func runDeployKeyList(c *Ctx, args []string) int { |
| 129 | 129 | }, func() screen { |
| 130 | 130 | rows := make([]row, len(ds)) |
| 131 | 131 | for i, d := range ds { |
| 132 | | rows[i] = rowOf(cRef(d.Fingerprint), cState(d.Mode), cText(d.Label), |
| 132 | rows[i] = rowOf(cFlexRef(d.Fingerprint), cState(d.Mode), cText(d.Label), |
| 133 | 133 | cMeta(d.Algo, "used "+c.usedText(d.LastUsedAt), c.expiresText(d.ExpiresAt, now))) |
| 134 | 134 | } |
| 135 | 135 | return listScreen("Deploy keys", rows, |
internal/control/runnerrepo.go
+1 −1
| @@ -139,7 +139,7 @@ func runRepoRunnerList(c *Ctx, args []string) int { |
| 139 | 139 | if r.LastSeen != "" { |
| 140 | 140 | seen = "seen " + relAge(r.LastSeen, termNow()) |
| 141 | 141 | } |
| 142 | | rows[i] = rowOf(cRef(r.Fingerprint), lead, cFlex(r.Username), cMeta(r.Algo, seen, held)) |
| 142 | rows[i] = rowOf(cFlexRef(r.Fingerprint), lead, cText(r.Username), cMeta(r.Algo, seen, held)) |
| 143 | 143 | } |
| 144 | 144 | return listScreen("Runners", rows, |
| 145 | 145 | action{"Runners", []string{"repo", "runner", "remove", repo.Path(), "<fingerprint>"}}, |
internal/control/sig.go
+1 −1
| @@ -362,7 +362,7 @@ func runRepoCommit(c *Ctx, args []string) int { |
| 362 | 362 | if d.CommitterEmail != "" && d.CommitterEmail != d.AuthorEmail { |
| 363 | 363 | s.fields = append(s.fields, field{"Committer", []cell{cText(d.CommitterEmail)}}) |
| 364 | 364 | } |
| 365 | | s.fields = append(s.fields, field{"Signature", []cell{cGlyph(d.Signature.State), cState(d.Signature.State), cMeta(d.Signature.Signer, d.Signature.Fingerprint)}}) |
| 365 | s.fields = append(s.fields, field{"Signed", []cell{cGlyph(d.Signature.State), cState(d.Signature.State), cMeta(d.Signature.Signer, d.Signature.Fingerprint)}}) |
| 366 | 366 | if !c.Term.Links { |
| 367 | 367 | s.fields = append(s.fields, field{"URL", []cell{cText(url)}}) |
| 368 | 368 | } |
internal/control/stage3repo_test.go
+24
| @@ -284,3 +284,27 @@ func TestRepoShowScreens(t *testing.T) { |
| 284 | 284 | t.Errorf("legend before the header:\n%s", out) |
| 285 | 285 | } |
| 286 | 286 | } |
| 287 | |
| 288 | // At 60 columns no screen line is wider than the terminal, apart from a |
| 289 | // suggested command: e2e's readonly test holds every read command to it. |
| 290 | func TestRepoScreensFitSixtyColumns(t *testing.T) { |
| 291 | st, repo, owner := repoListsFixture(t) |
| 292 | p := repo.Path() |
| 293 | for _, argv := range [][]string{ |
| 294 | {"repo", "deploy-key", "list", p}, |
| 295 | {"repo", "runner", "list", p}, |
| 296 | {"repo", "mirror", "list", p}, |
| 297 | } { |
| 298 | out, errOut := &bytes.Buffer{}, &bytes.Buffer{} |
| 299 | c := &Ctx{User: owner, Scope: "full", Store: st, Stdout: out, Stderr: errOut, Term: Term{Cols: 60}} |
| 300 | c.Cfg.Limits.WriteRate = -1 |
| 301 | if code := Dispatch(c, argv); code != protocol.ExitOK { |
| 302 | t.Fatalf("%v: exit %d: %s", argv, code, errOut) |
| 303 | } |
| 304 | for _, line := range strings.Split(out.String(), "\n") { |
| 305 | if cells(line) > 60 && !strings.HasPrefix(line, "gitbay ") { |
| 306 | t.Errorf("%v: %d cells: %q", argv, cells(line), line) |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 | } |
internal/control/table.go
+5
| @@ -57,6 +57,11 @@ func cGlyph(state string) cell { |
| 57 | 57 | return cell{kind: kindGlyph, s: g, sgr: sgr} |
| 58 | 58 | } |
| 59 | 59 | |
| 60 | // cFlexRef is an identifier too long to keep whole at every width, a |
| 61 | // key fingerprint: dim like a reference, but cut to fit like a title. |
| 62 | // Piped and --json output carry it whole. |
| 63 | func cFlexRef(s string) cell { return cell{kind: kindFlex, s: s, sgr: sgrDim} } |
| 64 | |
| 60 | 65 | // cYou is the mark for a row that waits on the viewer. |
| 61 | 66 | func cYou() cell { return cell{kind: kindGlyph, s: "●", sgr: sgrYellow} } |
| 62 | 67 | |