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 t.Errorf("plain repo list: %q", plain) 25 t.Errorf("plain repo list: %q", plain)
26 } 26 }
27 term, _, _ := inst.sshTerm(t, key, "80,color", "repo", "list") 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 t.Errorf("terminal repo list: %q", term) 29 t.Errorf("terminal repo list: %q", term)
30 } 30 }
31} 31}
@@ -75,10 +75,10 @@ func TestTermEnvOverMultiplexedSession(t *testing.T) {
75 exec.Command("ssh", "-o", "ControlPath="+sock, "-O", "exit", "git@127.0.0.1").Run() 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 t.Fatalf("master session: %q", out) 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 t.Errorf("second session kept the master's GITBAY_TERM: %q", out) 82 t.Errorf("second session kept the master's GITBAY_TERM: %q", out)
83 } 83 }
84 if out := mux(""); !strings.HasPrefix(out, "alice/app\t") { 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 }, func() screen { 129 }, func() screen {
130 rows := make([]row, len(ds)) 130 rows := make([]row, len(ds))
131 for i, d := range ds { 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 cMeta(d.Algo, "used "+c.usedText(d.LastUsedAt), c.expiresText(d.ExpiresAt, now))) 133 cMeta(d.Algo, "used "+c.usedText(d.LastUsedAt), c.expiresText(d.ExpiresAt, now)))
134 } 134 }
135 return listScreen("Deploy keys", rows, 135 return listScreen("Deploy keys", rows,
internal/control/runnerrepo.go +1 −1
@@ -139,7 +139,7 @@ func runRepoRunnerList(c *Ctx, args []string) int {
139 if r.LastSeen != "" { 139 if r.LastSeen != "" {
140 seen = "seen " + relAge(r.LastSeen, termNow()) 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 return listScreen("Runners", rows, 144 return listScreen("Runners", rows,
145 action{"Runners", []string{"repo", "runner", "remove", repo.Path(), "<fingerprint>"}}, 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 if d.CommitterEmail != "" && d.CommitterEmail != d.AuthorEmail { 362 if d.CommitterEmail != "" && d.CommitterEmail != d.AuthorEmail {
363 s.fields = append(s.fields, field{"Committer", []cell{cText(d.CommitterEmail)}}) 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 if !c.Term.Links { 366 if !c.Term.Links {
367 s.fields = append(s.fields, field{"URL", []cell{cText(url)}}) 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 t.Errorf("legend before the header:\n%s", out) 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.
290func 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 return cell{kind: kindGlyph, s: g, sgr: sgr} 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.
63func cFlexRef(s string) cell { return cell{kind: kindFlex, s: s, sgr: sgrDim} }
64
60// cYou is the mark for a row that waits on the viewer. 65// cYou is the mark for a row that waits on the viewer.
61func cYou() cell { return cell{kind: kindGlyph, s: "●", sgr: sgrYellow} } 66func cYou() cell { return cell{kind: kindGlyph, s: "●", sgr: sgrYellow} }
62 67