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) {
2525 t.Errorf("plain repo list: %q", plain)
2626 }
2727 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)") {
2929 t.Errorf("terminal repo list: %q", term)
3030 }
3131}
@@ -75,10 +75,10 @@ func TestTermEnvOverMultiplexedSession(t *testing.T) {
7575 exec.Command("ssh", "-o", "ControlPath="+sock, "-O", "exit", "git@127.0.0.1").Run()
7676 })
7777
78 if out := mux("80"); !strings.HasPrefix(out, "alice/app ") {
78 if out := mux("80"); !strings.HasPrefix(out, "Repositories (1)\nalice/app public") {
7979 t.Fatalf("master session: %q", out)
8080 }
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") {
8282 t.Errorf("second session kept the master's GITBAY_TERM: %q", out)
8383 }
8484 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 {
129129 }, func() screen {
130130 rows := make([]row, len(ds))
131131 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),
133133 cMeta(d.Algo, "used "+c.usedText(d.LastUsedAt), c.expiresText(d.ExpiresAt, now)))
134134 }
135135 return listScreen("Deploy keys", rows,
internal/control/runnerrepo.go +1 −1
@@ -139,7 +139,7 @@ func runRepoRunnerList(c *Ctx, args []string) int {
139139 if r.LastSeen != "" {
140140 seen = "seen " + relAge(r.LastSeen, termNow())
141141 }
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))
143143 }
144144 return listScreen("Runners", rows,
145145 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 {
362362 if d.CommitterEmail != "" && d.CommitterEmail != d.AuthorEmail {
363363 s.fields = append(s.fields, field{"Committer", []cell{cText(d.CommitterEmail)}})
364364 }
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)}})
366366 if !c.Term.Links {
367367 s.fields = append(s.fields, field{"URL", []cell{cText(url)}})
368368 }
internal/control/stage3repo_test.go +24
@@ -284,3 +284,27 @@ func TestRepoShowScreens(t *testing.T) {
284284 t.Errorf("legend before the header:\n%s", out)
285285 }
286286}
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 {
5757 return cell{kind: kindGlyph, s: g, sgr: sgr}
5858}
5959
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
6065// cYou is the mark for a row that waits on the viewer.
6166func cYou() cell { return cell{kind: kindGlyph, s: "●", sgr: sgrYellow} }
6267