Commit 77fb535e35

77fb535e3597945c4521a5cd4a4385e68ec4f8a6

parent: 9c0a74553a

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-02 04:16 UTC

control: repo settings show, deps status and commit as screens

The settings screen also shows required approvals, resolved threads,
code owners and website, which the piped output does not.

Ref #319

Layout: unified · split

internal/control/deps.go +29 −2
@@ -4,6 +4,7 @@ import (
4 "errors" 4 "errors"
5 "fmt" 5 "fmt"
6 "io" 6 "io"
7 "strconv"
7 8
8 "gitbay.org/gitbay/internal/policy" 9 "gitbay.org/gitbay/internal/policy"
9 "gitbay.org/gitbay/internal/protocol" 10 "gitbay.org/gitbay/internal/protocol"
@@ -92,9 +93,14 @@ func runDepsStatus(c *Ctx, args []string) int {
92 } 93 }
93 check, err := c.Store.DepCheckFor(repo.ID) 94 check, err := c.Store.DepCheckFor(repo.ID)
94 if errors.Is(err, store.ErrNotFound) { 95 if errors.Is(err, store.ErrNotFound) {
95 return c.emit(map[string]any{"enabled": false}, func(w io.Writer) { 96 return c.emitView(map[string]any{"enabled": false}, func(w io.Writer) {
96 v := c.view(w) 97 v := c.view(w)
97 v.fields("checks", fmt.Sprintf("off (repo deps enable %s)", repo.Path())) 98 v.fields("checks", fmt.Sprintf("off (repo deps enable %s)", repo.Path()))
99 }, func() screen {
100 return screen{
101 fields: []field{{"Checks", []cell{cText("off")}}},
102 actions: []action{{"Deps", []string{"repo", "deps", "enable", repo.Path()}}},
103 }
98 }) 104 })
99 } 105 }
100 if err != nil { 106 if err != nil {
@@ -109,7 +115,7 @@ func runDepsStatus(c *Ctx, args []string) int {
109 for _, r := range reports { 115 for _, r := range reports {
110 out.Behind = append(out.Behind, DepBehind{r.Ecosystem, r.Name, r.Current, r.Latest}) 116 out.Behind = append(out.Behind, DepBehind{r.Ecosystem, r.Name, r.Current, r.Latest})
111 } 117 }
112 return c.emit(out, func(w io.Writer) { 118 return c.emitView(out, func(w io.Writer) {
113 tracked := "" 119 tracked := ""
114 if check.IssueNumber != 0 { 120 if check.IssueNumber != 0 {
115 tracked = fmt.Sprintf("#%d", check.IssueNumber) 121 tracked = fmt.Sprintf("#%d", check.IssueNumber)
@@ -129,5 +135,26 @@ func runDepsStatus(c *Ctx, args []string) int {
129 } 135 }
130 tb.flush() 136 tb.flush()
131 } 137 }
138 }, func() screen {
139 state := []cell{cGlyph("ok"), cText("on")}
140 if check.LastError != "" {
141 state = []cell{cGlyph("failed"), cText("on")}
142 }
143 s := screen{fields: []field{
144 {"Checks", state},
145 {"Last check", []cell{cAge(check.LastCheck)}},
146 {"Error", []cell{cMark(check.LastError, sgrRed)}},
147 }}
148 if check.IssueNumber != 0 {
149 n := strconv.FormatInt(check.IssueNumber, 10)
150 s.fields = append(s.fields, field{"Tracked in", []cell{cLink("#"+n, c.siteURL(repo.Path(), "issues", n))}})
151 }
152 behind := section{title: "Behind", n: len(out.Behind)}
153 for _, b := range out.Behind {
154 behind.rows = append(behind.rows, rowOf(cRef(b.Name), cText(b.Ecosystem), cMeta(b.Current+" → "+b.Latest)))
155 }
156 s.sections = []section{behind}
157 s.actions = []action{{"Deps", []string{"repo", "deps", "disable", repo.Path()}}}
158 return s
132 }) 159 })
133} 160}
internal/control/repo.go +44 −1
@@ -811,7 +811,7 @@ func runSettingsShow(c *Ctx, args []string) int {
811 if code >= 0 { 811 if code >= 0 {
812 return code 812 return code
813 } 813 }
814 return c.emit(repo.Settings, func(w io.Writer) { 814 return c.emitView(repo.Settings, func(w io.Writer) {
815 v := c.view(w) 815 v := c.view(w)
816 v.title(repo.Path(), "settings", "") 816 v.title(repo.Path(), "settings", "")
817 v.fields( 817 v.fields(
@@ -824,6 +824,49 @@ func runSettingsShow(c *Ctx, args []string) int {
824 "git daemon", strconv.FormatBool(repo.Settings.GitDaemon), 824 "git daemon", strconv.FormatBool(repo.Settings.GitDaemon),
825 "archived", strconv.FormatBool(repo.Settings.Archived), 825 "archived", strconv.FormatBool(repo.Settings.Archived),
826 ) 826 )
827 }, func() screen {
828 set := repo.Settings
829 onOff := func(b bool) cell {
830 if b {
831 return cText("on")
832 }
833 return cMeta("off")
834 }
835 approvals := cMeta("off")
836 if set.RequireApprovals > 0 {
837 approvals = cText(strconv.Itoa(set.RequireApprovals))
838 }
839 list := func(xs []string) cell {
840 if len(xs) == 0 {
841 return cMeta("none")
842 }
843 return cText(strings.Join(xs, ", "))
844 }
845 mr := "on"
846 if set.RequireMR {
847 mr = "off"
848 }
849 s := screen{fields: []field{
850 {"Repo", []cell{cLink(repo.Path(), c.siteURL(repo.Path())), cMeta("settings")}},
851 {"Protected", []cell{list(set.ProtectedBranches)}},
852 {"Protected tags", []cell{list(set.ProtectedTags)}},
853 {"Require MR", []cell{onOff(set.RequireMR)}},
854 {"Require checks", []cell{onOff(set.RequireChecks)}},
855 {"Contexts", []cell{list(set.RequiredContexts)}},
856 {"Approvals", []cell{approvals}},
857 {"Resolved threads", []cell{onOff(set.RequireResolved)}},
858 {"Code owners", []cell{onOff(set.RequireCodeowners)}},
859 {"Signed commits", []cell{onOff(set.RequireSignedCommits)}},
860 {"Git daemon", []cell{onOff(set.GitDaemon)}},
861 {"Archived", []cell{onOff(set.Archived)}},
862 }, actions: []action{
863 {"Settings", []string{"repo", "settings", "protect", repo.Path(), "<branch>"}},
864 {"Settings", []string{"repo", "settings", "require-mr", repo.Path(), mr}},
865 }}
866 if set.Website != "" {
867 s.fields = append(s.fields, field{"Website", []cell{cText(set.Website)}})
868 }
869 return s
827 }) 870 })
828} 871}
829 872
internal/control/sig.go +20 −19
@@ -354,27 +354,28 @@ func runRepoCommit(c *Ctx, args []string) int {
354 fmt.Fprintf(w, "\n%s", d.Diff) 354 fmt.Fprintf(w, "\n%s", d.Diff)
355 return 355 return
356 } 356 }
357 v := c.view(w) 357 short, url := d.SHA[:min(10, len(d.SHA))], c.siteURL(repo.OwnerName, repo.Name, "commit", d.SHA[:min(12, len(d.SHA))])
358 v.title(d.SHA[:10], d.Subject, d.Signature.State) 358 s := screen{body: body, format: "text", fields: []field{
359 signer := d.Signature.Signer 359 {"Commit", []cell{cLink(short, url), cText(d.Subject)}},
360 if signer != "" && d.Signature.Fingerprint != "" { 360 {"Author", []cell{cText(fmt.Sprintf("%s <%s>", d.AuthorName, d.AuthorEmail)), cAge(d.Date)}},
361 signer += ", key " + d.Signature.Fingerprint 361 }}
362 if d.CommitterEmail != "" && d.CommitterEmail != d.AuthorEmail {
363 s.fields = append(s.fields, field{"Committer", []cell{cText(d.CommitterEmail)}})
362 } 364 }
363 v.fields( 365 s.fields = append(s.fields, field{"Signature", []cell{cGlyph(d.Signature.State), cState(d.Signature.State), cMeta(d.Signature.Signer, d.Signature.Fingerprint)}})
364 "author", fmt.Sprintf("%s <%s>, %s", d.AuthorName, d.AuthorEmail, c.when(d.Date)), 366 if !c.Term.Links {
365 "committer", d.CommitterEmail, 367 s.fields = append(s.fields, field{"URL", []cell{cText(url)}})
366 "signer", signer, 368 }
367 "url", c.siteURL(repo.OwnerName, repo.Name, "commit", d.SHA[:12]), 369 checks := section{title: "Checks", n: len(d.Checks)}
368 ) 370 for _, ch := range d.Checks {
369 v.text(body) 371 checks.rows = append(checks.rows, rowOf(cGlyph(ch.State), cFlex(ch.Context)))
370 if len(d.Checks) > 0 { 372 }
371 v.section("checks") 373 s.sections = []section{checks}
372 tb := c.table(w, "CHECK", "STATE") 374 s.actions = []action{
373 for _, ch := range d.Checks { 375 {"Read", []string{"repo", "log", repo.Path(), "--ref", short}},
374 tb.row(cText(ch.Context), cState(ch.State)) 376 {"Read", []string{"repo", "tree", repo.Path(), "--ref", short}},
375 }
376 tb.flush()
377 } 377 }
378 c.render(w, s)
378 fmt.Fprintf(w, "\n%s", c.Term.diff(d.Diff)) 379 fmt.Fprintf(w, "\n%s", c.Term.diff(d.Diff))
379 }) 380 })
380} 381}
internal/control/stage3repo_test.go +40
@@ -244,3 +244,43 @@ func TestRepoBrowseScreens(t *testing.T) {
244 } 244 }
245 checkLegend(t, out) 245 checkLegend(t, out)
246} 246}
247
248func TestRepoShowsPlainPinned(t *testing.T) {
249 st, repo, u, root := browseFixture(t)
250 p := repo.Path()
251 pinPlain(t, "repo-settings-show", dispatchIn(t, st, u, root, "", "repo", "settings", "show", p))
252 pinPlain(t, "repo-deps-status-off", dispatchIn(t, st, u, root, "", "repo", "deps", "status", p))
253 pinPlain(t, "repo-commit", dispatchIn(t, st, u, root, "", "repo", "commit", p, "aee4475ad9"))
254 dispatchIn(t, st, u, root, "", "repo", "deps", "enable", p)
255 pinPlain(t, "repo-deps-status", dispatchIn(t, st, u, root, "", "repo", "deps", "status", p))
256}
257
258func TestRepoShowScreens(t *testing.T) {
259 st, repo, u, root := browseFixture(t)
260 p := repo.Path()
261 for _, tc := range []struct {
262 argv []string
263 want []string
264 }{
265 {[]string{"repo", "settings", "show", p}, []string{"Repo:", "alice/app", "Require MR:", "off", "Approvals:"}},
266 {[]string{"repo", "deps", "status", p}, []string{"Checks: off\n", "gitbay repo deps enable alice/app"}},
267 {[]string{"repo", "commit", p, "aee4475ad9"}, []string{"Commit:", "aee4475ad9 docs: guide", "Author:", "t <t@example.test>", "gitbay repo log alice/app --ref aee4475ad9", "+# guide"}},
268 } {
269 out := atTerminalIn(t, st, u, root, tc.argv...)
270 for _, w := range tc.want {
271 if !strings.Contains(out, w) {
272 t.Errorf("%v: missing %q in:\n%s", tc.argv, w, out)
273 }
274 }
275 checkLegend(t, out)
276 }
277 dispatchIn(t, st, u, root, "", "repo", "deps", "enable", p)
278 out := atTerminalIn(t, st, u, root, "repo", "deps", "status", p)
279 if !strings.Contains(out, "Checks:") || !strings.Contains(out, "gitbay repo deps disable alice/app") {
280 t.Errorf("deps on:\n%s", out)
281 }
282 checkLegend(t, out)
283 if i, j := strings.Index(out, "gitbay repo deps"), strings.Index(out, "Checks:"); i < j {
284 t.Errorf("legend before the header:\n%s", out)
285 }
286}
internal/control/testdata/plain/repo-commit.txt added +16
@@ -0,0 +1,16 @@
1commit aee4475ad9033773a17e148aafa3105212ac9c10
2Author: t <t@example.test>
3Date: <time>
4
5 docs: guide
6
7 docs/guide.md | 1 +
8 1 file changed, 1 insertion(+)
9
10diff --git a/docs/guide.md b/docs/guide.md
11new file mode 100644
12index 0000000..cd51204
13--- /dev/null
14+++ b/docs/guide.md
15@@ -0,0 +1 @@
16+# guide
internal/control/testdata/plain/repo-deps-status-off.txt added +1
@@ -0,0 +1 @@
1 checks off (repo deps enable alice/app)
internal/control/testdata/plain/repo-deps-status.txt added +1
@@ -0,0 +1 @@
1 checks on
internal/control/testdata/plain/repo-settings-show.txt added +7
@@ -0,0 +1,7 @@
1alice/app settings
2
3 require mr false
4 require checks false
5 require signed commits false
6 git daemon false
7 archived false