Commit a9633bdd35

a9633bdd3543b8530dc9fd54af4d29f24472719e

parent: 54236b3f88

Verified · cmc ci/build: success ci/test: failure

cmc <hello@cleberg.net> · 2026-09-24 04:15 UTC

gitbay: summaries generated from the registry; grouped root help

Ref #254
cmd/gitbay/main.go +305 −262
@@ -12,6 +12,7 @@ import (
1212
1313 "github.com/spf13/cobra"
1414 "github.com/spf13/cobra/doc"
15 "golang.org/x/term"
1516
1617 "gitbay.org/gitbay/internal/protocol"
1718)
@@ -38,68 +39,55 @@ func newRoot() *cobra.Command {
3839 root.AddCommand(
3940 authCmd(),
4041 group("label", "issue labels",
41 pass("list", "labels with colour and use", passOpts{server: []string{"label", "list"}, needsRepo: true}),
42 pass("set", "create a label or set its colour: <label> [--color rrggbb|'']", passOpts{server: []string{"label", "set"}, needsRepo: true}),
43 pass("remove", "remove a label everywhere: <label>", passOpts{server: []string{"label", "remove"}, needsRepo: true}),
42 pass("list", passOpts{server: []string{"label", "list"}, needsRepo: true}),
43 pass("set", passOpts{server: []string{"label", "set"}, needsRepo: true}),
44 pass("remove", passOpts{server: []string{"label", "remove"}, needsRepo: true}),
4445 ),
4546 group("status", "commit statuses (CI)",
46 pass("set", "report a status: <sha> --context <c> --state <s> [--description d] [--url u]", passOpts{server: []string{"status", "set"}, needsRepo: true}),
47 pass("list", "statuses on a commit: <sha>", passOpts{server: []string{"status", "list"}, needsRepo: true}),
47 pass("set", passOpts{server: []string{"status", "set"}, needsRepo: true}),
48 pass("list", passOpts{server: []string{"status", "list"}, needsRepo: true}),
4849 ),
4950 group("build", "CI builds",
50 pass("list", "recent builds: <owner/name>", passOpts{server: []string{"build", "list"}, needsRepo: true}),
51 pass("show", "one build: <owner/name> <n>", passOpts{server: []string{"build", "show"}, needsRepo: true}),
52 pass("log", "a build's log: <owner/name> <n> [--follow]", passOpts{server: []string{"build", "log"}, needsRepo: true}),
53 pass("jobs", "list the jobs a trigger can name", passOpts{server: []string{"build", "jobs"}, needsRepo: true}),
54 pass("trigger", "queue a job now: <job>", passOpts{server: []string{"build", "trigger"}, needsRepo: true}),
55 pass("cancel", "withdraw a queued build: <n>", passOpts{server: []string{"build", "cancel"}, needsRepo: true}),
51 pass("list", passOpts{server: []string{"build", "list"}, needsRepo: true}),
52 pass("show", passOpts{server: []string{"build", "show"}, needsRepo: true}),
53 pass("log", passOpts{server: []string{"build", "log"}, needsRepo: true}),
54 pass("jobs", passOpts{server: []string{"build", "jobs"}, needsRepo: true}),
55 pass("trigger", passOpts{server: []string{"build", "trigger"}, needsRepo: true}),
56 pass("cancel", passOpts{server: []string{"build", "cancel"}, needsRepo: true}),
5657 ),
57 pass("dashboard", "one read for the account dashboard: pinned repos, open MRs, assigned issues, recent builds",
58 passOpts{server: []string{"dashboard"}}),
59 pass("feed", "activity on repositories you can reach [--limit n] [--cursor c]",
60 passOpts{server: []string{"feed"}}),
61 pass("explore", "public repositories on this instance [--limit n] [--cursor c]",
62 passOpts{server: []string{"explore"}}),
63 pass("search", "find repositories, issues and merge requests: <query> [--kind repo|issue|mr]",
64 passOpts{server: []string{"search"}}),
58 withShort(pass("dashboard", passOpts{server: []string{"dashboard"}}), "pinned repos, open MRs, assigned issues, recent builds"),
59 pass("feed", passOpts{server: []string{"feed"}}),
60 withShort(pass("explore", passOpts{server: []string{"explore"}}), "public repositories on this instance"),
61 withShort(pass("search", passOpts{server: []string{"search"}}), "find repositories, issues and merge requests"),
6562 group("notifications", "your notification inbox",
66 pass("list", "unread notifications, or [--all] [--limit n] [--cursor c]",
67 passOpts{server: []string{"notifications", "list"}}),
68 pass("read", "mark notifications read: <id>... | --all",
69 passOpts{server: []string{"notifications", "read"}}),
63 pass("list", passOpts{server: []string{"notifications", "list"}}),
64 pass("read", passOpts{server: []string{"notifications", "read"}}),
7065 group("settings", "notification preferences",
71 pass("show", "your notification preferences", passOpts{server: []string{"notifications", "settings", "show"}}),
72 pass("mail", "activity by mail as well as the inbox: on|off", passOpts{server: []string{"notifications", "settings", "mail"}}),
73 pass("watch", "every issue and merge request on repositories you can write to: on|off", passOpts{server: []string{"notifications", "settings", "watch"}}),
74 pass("push", "activity on your registered devices: on|off", passOpts{server: []string{"notifications", "settings", "push"}}),
66 pass("show", passOpts{server: []string{"notifications", "settings", "show"}}),
67 pass("mail", passOpts{server: []string{"notifications", "settings", "mail"}}),
68 pass("watch", passOpts{server: []string{"notifications", "settings", "watch"}}),
69 pass("push", passOpts{server: []string{"notifications", "settings", "push"}}),
7570 ),
7671 group("device", "Apple devices registered for push",
77 pass("add", "register a device, token on stdin: [--label name]",
78 passOpts{server: []string{"notifications", "device", "add"}, alwaysStdin: true, stdinWhat: "the device token"}),
79 pass("list", "your registered devices",
80 passOpts{server: []string{"notifications", "device", "list"}}),
81 pass("remove", "deregister a device: <id>",
82 passOpts{server: []string{"notifications", "device", "remove"}}),
72 pass("add", passOpts{server: []string{"notifications", "device", "add"}, alwaysStdin: true, stdinWhat: "the device token"}),
73 pass("list", passOpts{server: []string{"notifications", "device", "list"}}),
74 pass("remove", passOpts{server: []string{"notifications", "device", "remove"}}),
8375 ),
8476 ),
8577 group("wiki", "a repository's wiki pages",
86 pass("list", "list pages: [<owner/name>]", passOpts{server: []string{"wiki", "list"}, needsRepo: true}),
87 pass("show", "print a page: [<owner/name>] [<page>]", passOpts{server: []string{"wiki", "show"}, needsRepo: true}),
78 pass("list", passOpts{server: []string{"wiki", "list"}, needsRepo: true}),
79 pass("show", passOpts{server: []string{"wiki", "show"}, needsRepo: true}),
8880 ),
8981 group("snippet", "shared text files, outside any repository",
90 pass("create", "create from one file on stdin: <filename> [--description d] [--visibility public|unlisted|private] < file",
91 passOpts{server: []string{"snippet", "create"}, alwaysStdin: true, stdinWhat: "the file's text"}),
92 pass("show", "metadata and files: <id>", passOpts{server: []string{"snippet", "show"}}),
93 pass("list", "your snippets, or an owner's public ones: [<owner>] [--limit n] [--cursor c]",
94 passOpts{server: []string{"snippet", "list"}}),
95 pass("edit", "change description or visibility: <id> [--description d] [--visibility v]",
96 passOpts{server: []string{"snippet", "edit"}}),
97 pass("delete", "delete a snippet: <id>", passOpts{server: []string{"snippet", "delete"}}),
82 pass("create", passOpts{server: []string{"snippet", "create"}, alwaysStdin: true, stdinWhat: "the file's text"}),
83 pass("show", passOpts{server: []string{"snippet", "show"}}),
84 pass("list", passOpts{server: []string{"snippet", "list"}}),
85 pass("edit", passOpts{server: []string{"snippet", "edit"}}),
86 pass("delete", passOpts{server: []string{"snippet", "delete"}}),
9887 group("file", "the files in a snippet",
99 pass("set", "add or replace a file from stdin: <id> <filename> < file",
100 passOpts{server: []string{"snippet", "file", "set"}, alwaysStdin: true, stdinWhat: "the file's text"}),
101 pass("get", "print a file: <id> <filename> > file", passOpts{server: []string{"snippet", "file", "get"}}),
102 pass("remove", "remove a file: <id> <filename>", passOpts{server: []string{"snippet", "file", "remove"}}),
88 pass("set", passOpts{server: []string{"snippet", "file", "set"}, alwaysStdin: true, stdinWhat: "the file's text"}),
89 pass("get", passOpts{server: []string{"snippet", "file", "get"}}),
90 pass("remove", passOpts{server: []string{"snippet", "file", "remove"}}),
10391 ),
10492 ),
10593 repoCmd(),
@@ -111,52 +99,106 @@ func newRoot() *cobra.Command {
11199 webCmd(),
112100 orgCmd(),
113101 group("profile", "user and org profiles",
114 pass("show", "show a profile: [name]", passOpts{server: []string{"profile", "show"}}),
115 pass("set", "set your profile: [--description d] [--website url] [--link label|url]...",
116 passOpts{server: []string{"profile", "set"}}),
102 pass("show", passOpts{server: []string{"profile", "show"}}),
103 pass("set", passOpts{server: []string{"profile", "set"}}),
117104 ),
118105 webhookCmd(),
119106 remoteCmd(),
120107 initCmd(),
121 pass("register", "create an account on the default instance: gitbay register --username <n> --email <a> | --invite <code>",
122 passOpts{server: []string{"register"}}),
123 pass("audit", "instance audit log (admins): [--actor <user>|-] [--action <prefix>] [--since <duration|date>] [--limit <n>]", passOpts{server: []string{"audit"}}),
108 withShort(pass("register", passOpts{server: []string{"register"}}), "create an account on this instance"),
109 pass("audit", passOpts{server: []string{"audit"}}),
124110 group("admin", "instance administration (admins)",
125111 group("user", "accounts on this instance",
126 pass("list", "list accounts: [--state active|pending|disabled|admin] [--limit n] [--cursor c]", passOpts{server: []string{"admin", "user", "list"}}),
127 pass("show", "show an account: <username>", passOpts{server: []string{"admin", "user", "show"}}),
128 pass("promote", "make an account an instance admin: <username>", passOpts{server: []string{"admin", "user", "promote"}}),
129 pass("demote", "remove instance admin (never the last one): <username>", passOpts{server: []string{"admin", "user", "demote"}}),
130 pass("create", "create an account: <username> [--admin] [--email a [--verified]] [--key -] < key.pub", passOpts{server: []string{"admin", "user", "create"}, stdinOK: true}),
131 pass("disable", "suspend an account: <username>", passOpts{server: []string{"admin", "user", "disable"}}),
132 pass("enable", "restore a suspended account: <username>", passOpts{server: []string{"admin", "user", "enable"}}),
133 pass("delete", "delete an account that anchors nothing: <username> --yes", passOpts{server: []string{"admin", "user", "delete"}}),
134 pass("limits", "show or set repository and storage caps: <username> [--repos n|default] [--bytes n|default]", passOpts{server: []string{"admin", "user", "limits"}}),
112 pass("list", passOpts{server: []string{"admin", "user", "list"}}),
113 pass("show", passOpts{server: []string{"admin", "user", "show"}}),
114 pass("promote", passOpts{server: []string{"admin", "user", "promote"}}),
115 pass("demote", passOpts{server: []string{"admin", "user", "demote"}}),
116 pass("create", passOpts{server: []string{"admin", "user", "create"}, stdinOK: true}),
117 pass("disable", passOpts{server: []string{"admin", "user", "disable"}}),
118 pass("enable", passOpts{server: []string{"admin", "user", "enable"}}),
119 pass("delete", passOpts{server: []string{"admin", "user", "delete"}}),
120 pass("limits", passOpts{server: []string{"admin", "user", "limits"}}),
135121 ),
136122 group("email", "addresses on any account",
137 pass("verify", "mark an address verified by admin assertion: <username> <address>", passOpts{server: []string{"admin", "email", "verify"}}),
123 pass("verify", passOpts{server: []string{"admin", "email", "verify"}}),
138124 ),
139 pass("invite", "issue a registration invite and mail its code: --email <address>", passOpts{server: []string{"admin", "invite"}}),
140 pass("stats", "instance statistics: counts and per-repository disk usage", passOpts{server: []string{"admin", "stats"}}),
141 withSub(pass("runners", "the build queue and runner keys: last poll, scope, the build each holds", passOpts{server: []string{"admin", "runners"}}),
142 pass("remove", "drop a key's heartbeat row: <fingerprint>", passOpts{server: []string{"admin", "runners", "remove"}}),
143 pass("forget", "alias of remove: <fingerprint>", passOpts{server: []string{"admin", "runners", "forget"}})),
125 pass("invite", passOpts{server: []string{"admin", "invite"}}),
126 pass("stats", passOpts{server: []string{"admin", "stats"}}),
127 withSub(pass("runners", passOpts{server: []string{"admin", "runners"}}),
128 pass("remove", passOpts{server: []string{"admin", "runners", "remove"}}),
129 pass("forget", passOpts{server: []string{"admin", "runners", "forget"}})),
144130 group("repo", "any repository, for moderation (audited)",
145 pass("list", "every repository with size and last push: [--owner o] [--visibility v] [--limit n] [--cursor c]", passOpts{server: []string{"admin", "repo", "list"}}),
146 pass("archive", "archive a repository: <owner/name>", passOpts{server: []string{"admin", "repo", "archive"}}),
147 pass("unarchive", "unarchive a repository: <owner/name>", passOpts{server: []string{"admin", "repo", "unarchive"}}),
148 pass("visibility", "set visibility: <owner/name> public|private", passOpts{server: []string{"admin", "repo", "visibility"}}),
149 pass("delete", "delete a repository: <owner/name> --yes", passOpts{server: []string{"admin", "repo", "delete"}}),
131 pass("list", passOpts{server: []string{"admin", "repo", "list"}}),
132 pass("archive", passOpts{server: []string{"admin", "repo", "archive"}}),
133 pass("unarchive", passOpts{server: []string{"admin", "repo", "unarchive"}}),
134 pass("visibility", passOpts{server: []string{"admin", "repo", "visibility"}}),
135 pass("delete", passOpts{server: []string{"admin", "repo", "delete"}}),
150136 ),
151137 group("mr", "merge requests in any repository (audited)",
152 pass("prune", "drop merged or closed MRs' head refs and the objects only they kept: <owner/name> <n>... --yes", passOpts{server: []string{"admin", "mr", "prune"}}),
138 pass("prune", passOpts{server: []string{"admin", "mr", "prune"}}),
153139 ),
154140 ),
155141 manCmd(root),
156142 )
143 defaultHelp := root.HelpFunc()
144 root.SetHelpFunc(func(cmd *cobra.Command, args []string) {
145 if cmd == root {
146 rootHelp(root)
147 return
148 }
149 defaultHelp(cmd, args)
150 })
157151 return root
158152}
159153
154// rootSection is one heading in gitbay --help: a title and the root
155// commands that belong under it.
156type rootSection struct {
157 title string
158 names []string
159}
160
161var rootSections = []rootSection{
162 {"WORK", []string{"issue", "mr", "build", "release", "milestone", "label", "search"}},
163 {"REPOSITORIES", []string{"repo", "wiki", "status", "webhook", "init"}},
164 {"YOU", []string{"dashboard", "feed", "notifications", "auth", "profile", "snippet", "web"}},
165 {"INSTANCE", []string{"org", "explore", "register", "migrate", "remote", "admin", "audit", "man"}},
166}
167
168// rootHelp is gitbay --help: the nouns grouped by what they are for.
169func rootHelp(root *cobra.Command) {
170 byName := map[string]*cobra.Command{}
171 wide := 0
172 for _, c := range root.Commands() {
173 byName[c.Name()] = c
174 if !c.Hidden {
175 wide = max(wide, len(c.Name()))
176 }
177 }
178 fmt.Println("gitbay: command-line client for a gitbay forge")
179 fmt.Println()
180 fmt.Println("USAGE")
181 fmt.Println(" gitbay <command> [<owner/name>] [flags]")
182 for _, s := range rootSections {
183 var rows [][2]string
184 for _, n := range s.names {
185 if c := byName[n]; c != nil && !c.Hidden {
186 rows = append(rows, [2]string{n, c.Short})
187 }
188 }
189 if len(rows) == 0 {
190 continue
191 }
192 fmt.Println()
193 fmt.Println(s.title)
194 for _, r := range rows {
195 fmt.Printf(" %-*s %s\n", wide, r[0], r[1])
196 }
197 }
198 fmt.Println()
199 fmt.Println("gitbay <command> --help for its verbs; gitbay help <prefix> for the server reference.")
200}
201
160202// serverPath is the annotation key holding a passthrough command's
161203// server-side path, so the tree can be checked against the registry.
162204const serverPath = "gitbay.server_path"
@@ -197,10 +239,10 @@ func (o passOpts) stdinModeName() string {
197239 return "none"
198240}
199241
200func pass(use, short string, o passOpts) *cobra.Command {
242func pass(use string, o passOpts) *cobra.Command {
201243 return &cobra.Command{
202244 Use: use,
203 Short: short,
245 Short: summaries[strings.Join(o.server, " ")],
204246 Annotations: map[string]string{
205247 serverPath: strings.Join(o.server, " "),
206248 stdinMode: o.stdinModeName(),
@@ -222,6 +264,17 @@ func pass(use, short string, o passOpts) *cobra.Command {
222264 }
223265}
224266
267// withShort overrides a passthrough command's one-line summary. Used
268// where the registry's own Summary for a single-verb noun (dashboard,
269// explore, search, register) reads differently from nounSummaries, which
270// is what a grouped noun's header and the root help both show; the two
271// must agree (TestGroupsSayWhatTheServerSays), so these few take the
272// noun's wording instead of the command's.
273func withShort(cmd *cobra.Command, short string) *cobra.Command {
274 cmd.Short = short
275 return cmd
276}
277
225278// runServerHelp prints the registry's usage for one command.
226279func runServerHelp(o passOpts) int {
227280 t, err := resolveTarget()
@@ -335,12 +388,18 @@ func group(use, short string, subs ...*cobra.Command) *cobra.Command {
335388}
336389
337390// serverHelp prints the registry's usage for a prefix and reports whether
338// it did.
391// it did. At a terminal it goes through the terminal-aware path, so it
392// gets the same --term=<cols>[,color] treatment (and layout) as any other
393// command; piped, it stays a quiet capture, so a network or lookup
394// failure falls back to cobra's local help without noise.
339395func serverHelp(prefix string) bool {
340396 t, err := resolveTarget()
341397 if err != nil {
342398 return false
343399 }
400 if term.IsTerminal(int(os.Stdout.Fd())) {
401 return runSSH(t, []string{"help", prefix}, strings.NewReader("")) == 0
402 }
344403 out, code := sshCapture(t, []string{"help", prefix})
345404 if code != 0 || out == "" {
346405 return false
@@ -368,8 +427,7 @@ func local(use, short string, fn func(args []string) int) *cobra.Command {
368427}
369428
370429func authCmd() *cobra.Command {
371 keysAdd := pass("add", "register an SSH public key (reads the key from stdin or --file -)",
372 passOpts{server: []string{"keys", "add"}, alwaysStdin: true, stdinWhat: "an SSH public key"})
430 keysAdd := pass("add", passOpts{server: []string{"keys", "add"}, alwaysStdin: true, stdinWhat: "an SSH public key"})
373431 // keys add always reads stdin on the server; wire it through directly.
374432 keysAdd.RunE = func(cmd *cobra.Command, args []string) error {
375433 t, err := resolveTarget()
@@ -405,212 +463,197 @@ func authCmd() *cobra.Command {
405463 },
406464 }
407465 tokens := group("token", "API tokens (minted over SSH, used with the JSON API)",
408 pass("create", "mint a token: --name <n> [--scope full|read] [--ttl 30d]", passOpts{server: []string{"token", "create"}}),
409 pass("list", "list API tokens", passOpts{server: []string{"token", "list"}}),
410 pass("revoke", "revoke a token by name", passOpts{server: []string{"token", "revoke"}}),
466 pass("create", passOpts{server: []string{"token", "create"}}),
467 pass("list", passOpts{server: []string{"token", "list"}}),
468 pass("revoke", passOpts{server: []string{"token", "revoke"}}),
411469 )
412470 return group("auth", "identity: whoami, SSH and PGP keys",
413 pass("export", "write your account bundle (a user-level backup) to stdout",
414 passOpts{server: []string{"account", "export"}}),
471 pass("export", passOpts{server: []string{"account", "export"}}),
415472 tokens,
416 pass("whoami", "show the authenticated account", passOpts{server: []string{"whoami"}}),
473 pass("whoami", passOpts{server: []string{"whoami"}}),
417474 group("keys", "manage SSH keys",
418 pass("list", "list registered SSH keys", passOpts{server: []string{"keys", "list"}}),
475 pass("list", passOpts{server: []string{"keys", "list"}}),
419476 keysAdd,
420 pass("label", "name a key: <fingerprint> [<text>]; no text clears it", passOpts{server: []string{"keys", "label"}}),
421 pass("remove", "remove an SSH key by fingerprint", passOpts{server: []string{"keys", "remove"}}),
477 pass("label", passOpts{server: []string{"keys", "label"}}),
478 pass("remove", passOpts{server: []string{"keys", "remove"}}),
422479 ),
423480 group("email", "manage email addresses",
424 pass("add", "add an address and get a verification code by mail", passOpts{server: []string{"email", "add"}}),
425 pass("verify", "confirm a verification code", passOpts{server: []string{"email", "verify"}}),
426 pass("list", "list the addresses on your account", passOpts{server: []string{"email", "list"}}),
427 pass("remove", "remove an address; not the primary, nor the last verified one", passOpts{server: []string{"email", "remove"}}),
428 pass("primary", "make a verified address the primary", passOpts{server: []string{"email", "primary"}}),
481 pass("add", passOpts{server: []string{"email", "add"}}),
482 pass("verify", passOpts{server: []string{"email", "verify"}}),
483 pass("list", passOpts{server: []string{"email", "list"}}),
484 pass("remove", passOpts{server: []string{"email", "remove"}}),
485 pass("primary", passOpts{server: []string{"email", "primary"}}),
429486 ),
430487 group("pgp", "manage OpenPGP keys",
431 pass("list", "list registered PGP keys", passOpts{server: []string{"pgp", "list"}}),
488 pass("list", passOpts{server: []string{"pgp", "list"}}),
432489 pgpAdd,
433 pass("remove", "remove a PGP key by fingerprint", passOpts{server: []string{"pgp", "remove"}}),
490 pass("remove", passOpts{server: []string{"pgp", "remove"}}),
434491 ),
435492 )
436493}
437494
438495func repoCmd() *cobra.Command {
439496 return group("repo", "create and manage repositories",
440 pass("create", "create a repository: gitbay repo create <owner/name> [--private]",
441 passOpts{server: []string{"repo", "create"}}),
442 pass("list", "list repositories you own or can access [--limit n] [--cursor c]", passOpts{server: []string{"repo", "list"}}),
443 pass("show", "show repository details", passOpts{server: []string{"repo", "show"}, needsRepo: true}),
444 pass("log", "commit log with signature states", passOpts{server: []string{"repo", "log"}, needsRepo: true}),
445 pass("transfer", "move a repository to another owner: <new-owner>", passOpts{server: []string{"repo", "transfer"}, needsRepo: true}),
446 pass("rename", "rename a repository: <new-name> (clone URLs change)", passOpts{server: []string{"repo", "rename"}, needsRepo: true}),
447 pass("delete", "delete a repository (--yes)", passOpts{server: []string{"repo", "delete"}, needsRepo: true}),
448 pass("fork", "fork a repository to you or an organization", passOpts{server: []string{"repo", "fork"}, needsRepo: true}),
449 pass("search", "find repositories by name, description, or topic: <query>", passOpts{server: []string{"repo", "search"}}),
450 pass("grep", "search file contents: <query> [--ref <ref>]", passOpts{server: []string{"repo", "grep"}, needsRepo: true}),
451 pass("diff", "the patch between two refs: <base> <head>", passOpts{server: []string{"repo", "diff"}, needsRepo: true}),
452 pass("tree", "list a directory: [<path>] [--ref <ref>]", passOpts{server: []string{"repo", "tree"}, needsRepo: true}),
453 pass("cat", "read a file: <path> [--ref <ref>]", passOpts{server: []string{"repo", "cat"}, needsRepo: true}),
454 pass("blame", "attribute lines to commits: <path> [--ref <ref>] [--from <n>] [--to <n>]",
455 passOpts{server: []string{"repo", "blame"}, needsRepo: true}),
456 pass("commit", "show one commit with its patch: <sha>",
457 passOpts{server: []string{"repo", "commit"}, needsRepo: true}),
458 pass("commit-file", "write a file and commit it: <path> [--ref <ref>] [--message <m>] --file -",
459 passOpts{server: []string{"repo", "commit-file"}, needsRepo: true, stdinOK: true}),
460 pass("refs", "list branches and tags", passOpts{server: []string{"repo", "refs"}, needsRepo: true}),
461 pass("download", "write a tar.gz of a ref to stdout: [--ref <r>] > repo.tar.gz",
462 passOpts{server: []string{"repo", "download"}, needsRepo: true}),
463 pass("pin", "pin a repository to your dashboard", passOpts{server: []string{"repo", "pin"}, needsRepo: true}),
464 pass("unpin", "unpin a repository", passOpts{server: []string{"repo", "unpin"}, needsRepo: true}),
465 pass("bookmark", "bookmark a repository to come back to", passOpts{server: []string{"repo", "bookmark"}, needsRepo: true}),
466 pass("unbookmark", "remove a bookmark", passOpts{server: []string{"repo", "unbookmark"}, needsRepo: true}),
467 pass("bookmarks", "list the repositories you have bookmarked", passOpts{server: []string{"repo", "bookmarks"}}),
468 pass("watch", "hear about all activity on a repository", passOpts{server: []string{"repo", "watch"}, needsRepo: true}),
469 pass("unwatch", "stop watching a repository", passOpts{server: []string{"repo", "unwatch"}, needsRepo: true}),
470 pass("mute", "mute a repository, including work you are part of", passOpts{server: []string{"repo", "mute"}, needsRepo: true}),
471 pass("archive", "archive a repository (read-only)", passOpts{server: []string{"repo", "archive"}, needsRepo: true}),
472 pass("unarchive", "unarchive a repository", passOpts{server: []string{"repo", "unarchive"}, needsRepo: true}),
497 pass("create", passOpts{server: []string{"repo", "create"}}),
498 pass("list", passOpts{server: []string{"repo", "list"}}),
499 pass("show", passOpts{server: []string{"repo", "show"}, needsRepo: true}),
500 pass("log", passOpts{server: []string{"repo", "log"}, needsRepo: true}),
501 pass("transfer", passOpts{server: []string{"repo", "transfer"}, needsRepo: true}),
502 pass("rename", passOpts{server: []string{"repo", "rename"}, needsRepo: true}),
503 pass("delete", passOpts{server: []string{"repo", "delete"}, needsRepo: true}),
504 pass("fork", passOpts{server: []string{"repo", "fork"}, needsRepo: true}),
505 pass("search", passOpts{server: []string{"repo", "search"}}),
506 pass("grep", passOpts{server: []string{"repo", "grep"}, needsRepo: true}),
507 pass("diff", passOpts{server: []string{"repo", "diff"}, needsRepo: true}),
508 pass("tree", passOpts{server: []string{"repo", "tree"}, needsRepo: true}),
509 pass("cat", passOpts{server: []string{"repo", "cat"}, needsRepo: true}),
510 pass("blame", passOpts{server: []string{"repo", "blame"}, needsRepo: true}),
511 pass("commit", passOpts{server: []string{"repo", "commit"}, needsRepo: true}),
512 pass("commit-file", passOpts{server: []string{"repo", "commit-file"}, needsRepo: true, stdinOK: true}),
513 pass("refs", passOpts{server: []string{"repo", "refs"}, needsRepo: true}),
514 pass("download", passOpts{server: []string{"repo", "download"}, needsRepo: true}),
515 pass("pin", passOpts{server: []string{"repo", "pin"}, needsRepo: true}),
516 pass("unpin", passOpts{server: []string{"repo", "unpin"}, needsRepo: true}),
517 pass("bookmark", passOpts{server: []string{"repo", "bookmark"}, needsRepo: true}),
518 pass("unbookmark", passOpts{server: []string{"repo", "unbookmark"}, needsRepo: true}),
519 pass("bookmarks", passOpts{server: []string{"repo", "bookmarks"}}),
520 pass("watch", passOpts{server: []string{"repo", "watch"}, needsRepo: true}),
521 pass("unwatch", passOpts{server: []string{"repo", "unwatch"}, needsRepo: true}),
522 pass("mute", passOpts{server: []string{"repo", "mute"}, needsRepo: true}),
523 pass("archive", passOpts{server: []string{"repo", "archive"}, needsRepo: true}),
524 pass("unarchive", passOpts{server: []string{"repo", "unarchive"}, needsRepo: true}),
473525 local("clone", "clone via ssh: gitbay repo clone <owner/name> [dir]", cmdRepoClone),
474526 importCmd(),
475 pass("import-issues", "import GitHub issue/PR history: --from <ghowner/ghrepo> [--token-stdin]",
476 passOpts{server: []string{"repo", "import-issues"}, needsRepo: true, stdinOK: true}),
527 pass("import-issues", passOpts{server: []string{"repo", "import-issues"}, needsRepo: true, stdinOK: true}),
477528 group("deploy-key", "repository-bound CI keys",
478 pass("add", "bind a key: [--rw] < key.pub", passOpts{server: []string{"repo", "deploy-key", "add"}, needsRepo: true, alwaysStdin: true, stdinWhat: "an SSH public key"}),
479 pass("list", "list deploy keys", passOpts{server: []string{"repo", "deploy-key", "list"}, needsRepo: true}),
480 pass("remove", "remove a deploy key: <fingerprint>", passOpts{server: []string{"repo", "deploy-key", "remove"}, needsRepo: true}),
529 pass("add", passOpts{server: []string{"repo", "deploy-key", "add"}, needsRepo: true, alwaysStdin: true, stdinWhat: "an SSH public key"}),
530 pass("list", passOpts{server: []string{"repo", "deploy-key", "list"}, needsRepo: true}),
531 pass("remove", passOpts{server: []string{"repo", "deploy-key", "remove"}, needsRepo: true}),
481532 ),
482533 group("runner", "runners attached to a repository",
483 pass("add", "attach a runner's public key: < key.pub", passOpts{server: []string{"repo", "runner", "add"}, needsRepo: true, alwaysStdin: true, stdinWhat: "an SSH public key"}),
484 pass("list", "list attached runners", passOpts{server: []string{"repo", "runner", "list"}, needsRepo: true}),
485 pass("remove", "detach a runner: <fingerprint>", passOpts{server: []string{"repo", "runner", "remove"}, needsRepo: true}),
534 pass("add", passOpts{server: []string{"repo", "runner", "add"}, needsRepo: true, alwaysStdin: true, stdinWhat: "an SSH public key"}),
535 pass("list", passOpts{server: []string{"repo", "runner", "list"}, needsRepo: true}),
536 pass("remove", passOpts{server: []string{"repo", "runner", "remove"}, needsRepo: true}),
486537 ),
487538 group("mirror", "sync with a foreign remote",
488 pass("add", "add a mirror: <https-url> --direction push|pull [--username <u>] [--token-stdin]",
489 passOpts{server: []string{"repo", "mirror", "add"}, needsRepo: true, stdinOK: true}),
490 pass("list", "list mirrors with sync status", passOpts{server: []string{"repo", "mirror", "list"}, needsRepo: true}),
491 pass("remove", "remove a mirror: <id>", passOpts{server: []string{"repo", "mirror", "remove"}, needsRepo: true}),
492 pass("sync", "schedule an immediate sync", passOpts{server: []string{"repo", "mirror", "sync"}, needsRepo: true}),
539 pass("add", passOpts{server: []string{"repo", "mirror", "add"}, needsRepo: true, stdinOK: true}),
540 pass("list", passOpts{server: []string{"repo", "mirror", "list"}, needsRepo: true}),
541 pass("remove", passOpts{server: []string{"repo", "mirror", "remove"}, needsRepo: true}),
542 pass("sync", passOpts{server: []string{"repo", "mirror", "sync"}, needsRepo: true}),
493543 ),
494544 group("deps", "check dependencies against upstream registries",
495 pass("enable", "check this repo's dependencies for updates", passOpts{server: []string{"repo", "deps", "enable"}, needsRepo: true}),
496 pass("disable", "stop checking dependencies", passOpts{server: []string{"repo", "deps", "disable"}, needsRepo: true}),
497 pass("status", "show check state and what is behind", passOpts{server: []string{"repo", "deps", "status"}, needsRepo: true}),
545 pass("enable", passOpts{server: []string{"repo", "deps", "enable"}, needsRepo: true}),
546 pass("disable", passOpts{server: []string{"repo", "deps", "disable"}, needsRepo: true}),
547 pass("status", passOpts{server: []string{"repo", "deps", "status"}, needsRepo: true}),
498548 ),
499549 group("secret", "build secrets (values on stdin, injected into build env)",
500 pass("set", "set a secret: <NAME> (value on stdin)", passOpts{server: []string{"repo", "secret", "set"}, needsRepo: true, alwaysStdin: true, stdinWhat: "the secret value", stdinSecret: true}),
501 pass("list", "list secret names", passOpts{server: []string{"repo", "secret", "list"}, needsRepo: true}),
502 pass("remove", "remove a secret: <NAME>", passOpts{server: []string{"repo", "secret", "remove"}, needsRepo: true}),
550 pass("set", passOpts{server: []string{"repo", "secret", "set"}, needsRepo: true, alwaysStdin: true, stdinWhat: "the secret value", stdinSecret: true}),
551 pass("list", passOpts{server: []string{"repo", "secret", "list"}, needsRepo: true}),
552 pass("remove", passOpts{server: []string{"repo", "secret", "remove"}, needsRepo: true}),
503553 ),
504554 group("domain", "custom domains for the pages branch",
505 pass("add", "claim a domain (verify with a DNS TXT record): <domain>", passOpts{server: []string{"repo", "domain", "add"}, needsRepo: true}),
506 pass("verify", "check the DNS challenge and activate a claim: <domain>", passOpts{server: []string{"repo", "domain", "verify"}, needsRepo: true}),
507 pass("list", "list custom pages domains", passOpts{server: []string{"repo", "domain", "list"}, needsRepo: true}),
508 pass("remove", "remove a custom pages domain: <domain>", passOpts{server: []string{"repo", "domain", "remove"}, needsRepo: true}),
555 pass("add", passOpts{server: []string{"repo", "domain", "add"}, needsRepo: true}),
556 pass("verify", passOpts{server: []string{"repo", "domain", "verify"}, needsRepo: true}),
557 pass("list", passOpts{server: []string{"repo", "domain", "list"}, needsRepo: true}),
558 pass("remove", passOpts{server: []string{"repo", "domain", "remove"}, needsRepo: true}),
509559 ),
510560 group("topics", "free-form repository tags",
511 pass("list", "list topics", passOpts{server: []string{"repo", "topics"}, needsRepo: true}),
512 pass("add", "add topics: <topic>...", passOpts{server: []string{"repo", "topics", "add"}, needsRepo: true}),
513 pass("remove", "remove topics: <topic>...", passOpts{server: []string{"repo", "topics", "remove"}, needsRepo: true}),
561 pass("list", passOpts{server: []string{"repo", "topics"}, needsRepo: true}),
562 pass("add", passOpts{server: []string{"repo", "topics", "add"}, needsRepo: true}),
563 pass("remove", passOpts{server: []string{"repo", "topics", "remove"}, needsRepo: true}),
514564 ),
515565 group("access", "manage access grants",
516 pass("grant", "grant access: ... <user> read|write|admin", passOpts{server: []string{"repo", "access", "grant"}, needsRepo: true}),
517 pass("revoke", "revoke access: ... <user>", passOpts{server: []string{"repo", "access", "revoke"}, needsRepo: true}),
518 pass("list", "list access grants", passOpts{server: []string{"repo", "access", "list"}, needsRepo: true}),
566 pass("grant", passOpts{server: []string{"repo", "access", "grant"}, needsRepo: true}),
567 pass("revoke", passOpts{server: []string{"repo", "access", "revoke"}, needsRepo: true}),
568 pass("list", passOpts{server: []string{"repo", "access", "list"}, needsRepo: true}),
519569 ),
520570 group("settings", "repository settings",
521 pass("show", "show settings", passOpts{server: []string{"repo", "settings", "show"}, needsRepo: true}),
522 pass("protect", "protect a branch", passOpts{server: []string{"repo", "settings", "protect"}, needsRepo: true}),
523 pass("unprotect", "unprotect a branch", passOpts{server: []string{"repo", "settings", "unprotect"}, needsRepo: true}),
524 pass("protect-tag", "protect tags matching a glob: <glob>", passOpts{server: []string{"repo", "settings", "protect-tag"}, needsRepo: true}),
525 pass("unprotect-tag", "drop a protected-tag glob: <glob>", passOpts{server: []string{"repo", "settings", "unprotect-tag"}, needsRepo: true}),
526 pass("default-branch", "set the default branch: <branch>", passOpts{server: []string{"repo", "settings", "default-branch"}, needsRepo: true}),
527 pass("require-approvals", "require N fresh approvals to merge: <n>", passOpts{server: []string{"repo", "settings", "require-approvals"}, needsRepo: true}),
528 pass("require-resolved", "require threads resolved to merge: on|off", passOpts{server: []string{"repo", "settings", "require-resolved"}, needsRepo: true}),
529 pass("require-codeowners", "require an owner's approval per covered file: on|off", passOpts{server: []string{"repo", "settings", "require-codeowners"}, needsRepo: true}),
530 pass("require-checks", "gate merges on green statuses: ... on|off", passOpts{server: []string{"repo", "settings", "require-checks"}, needsRepo: true}),
531 pass("visibility", "set repository visibility: public|private", passOpts{server: []string{"repo", "settings", "visibility"}, needsRepo: true}),
532 pass("require-signed", "require verified commit signatures: ... on|off", passOpts{server: []string{"repo", "settings", "require-signed"}, needsRepo: true}),
533 pass("require-mr", "protected branches take changes through merge requests only: on|off", passOpts{server: []string{"repo", "settings", "require-mr"}, needsRepo: true}),
534 pass("description", "set the repository description: <text>", passOpts{server: []string{"repo", "settings", "description"}, needsRepo: true}),
535 pass("website", "set the repository website: <url> ('' clears)", passOpts{server: []string{"repo", "settings", "website"}, needsRepo: true}),
536 pass("git-daemon", "expose over git://: ... on|off", passOpts{server: []string{"repo", "settings", "git-daemon"}, needsRepo: true}),
571 pass("show", passOpts{server: []string{"repo", "settings", "show"}, needsRepo: true}),
572 pass("protect", passOpts{server: []string{"repo", "settings", "protect"}, needsRepo: true}),
573 pass("unprotect", passOpts{server: []string{"repo", "settings", "unprotect"}, needsRepo: true}),
574 pass("protect-tag", passOpts{server: []string{"repo", "settings", "protect-tag"}, needsRepo: true}),
575 pass("unprotect-tag", passOpts{server: []string{"repo", "settings", "unprotect-tag"}, needsRepo: true}),
576 pass("default-branch", passOpts{server: []string{"repo", "settings", "default-branch"}, needsRepo: true}),
577 pass("require-approvals", passOpts{server: []string{"repo", "settings", "require-approvals"}, needsRepo: true}),
578 pass("require-resolved", passOpts{server: []string{"repo", "settings", "require-resolved"}, needsRepo: true}),
579 pass("require-codeowners", passOpts{server: []string{"repo", "settings", "require-codeowners"}, needsRepo: true}),
580 pass("require-checks", passOpts{server: []string{"repo", "settings", "require-checks"}, needsRepo: true}),
581 pass("visibility", passOpts{server: []string{"repo", "settings", "visibility"}, needsRepo: true}),
582 pass("require-signed", passOpts{server: []string{"repo", "settings", "require-signed"}, needsRepo: true}),
583 pass("require-mr", passOpts{server: []string{"repo", "settings", "require-mr"}, needsRepo: true}),
584 pass("description", passOpts{server: []string{"repo", "settings", "description"}, needsRepo: true}),
585 pass("website", passOpts{server: []string{"repo", "settings", "website"}, needsRepo: true}),
586 pass("git-daemon", passOpts{server: []string{"repo", "settings", "git-daemon"}, needsRepo: true}),
537587 ),
538588 )
539589}
540590
541591func issueCmd() *cobra.Command {
542592 return group("issue", "issues",
543 pass("create", "open an issue: --title <t> [--body|--file -|$EDITOR]",
544 passOpts{server: []string{"issue", "create"}, needsRepo: true, stdinOK: true, editor: "issue"}),
545 pass("list", "list issues [--state open|closed|all] [--label l] [--assignee u] [--author u] [--milestone m|none] [--limit n] [--cursor c]", passOpts{server: []string{"issue", "list"}, needsRepo: true}),
546 pass("show", "show an issue with comments", passOpts{server: []string{"issue", "show"}, needsRepo: true}),
547 pass("comment", "comment on an issue [--message|--file -|$EDITOR]",
548 passOpts{server: []string{"issue", "comment"}, needsRepo: true, stdinOK: true, editor: "comment"}),
549 pass("close", "close an issue", passOpts{server: []string{"issue", "close"}, needsRepo: true}),
550 pass("reopen", "reopen an issue", passOpts{server: []string{"issue", "reopen"}, needsRepo: true}),
551 pass("label", "add or remove labels: [--add <l>]... [--remove <l>]...", passOpts{server: []string{"issue", "label"}, needsRepo: true}),
552 pass("assign", "assign users: [--add <u>]... [--remove <u>]...", passOpts{server: []string{"issue", "assign"}, needsRepo: true}),
553 pass("edit", "edit title or body: <n> [--title <t>] [--body <b>|--file -]", passOpts{server: []string{"issue", "edit"}, needsRepo: true, stdinOK: true}),
554 pass("milestone", "set or clear the milestone: <n> <title|none>", passOpts{server: []string{"issue", "milestone"}, needsRepo: true}),
555 pass("templates", "list issue templates (.gitbay/issue-template*.md)", passOpts{server: []string{"issue", "templates"}, needsRepo: true}),
593 pass("create", passOpts{server: []string{"issue", "create"}, needsRepo: true, stdinOK: true, editor: "issue"}),
594 pass("list", passOpts{server: []string{"issue", "list"}, needsRepo: true}),
595 pass("show", passOpts{server: []string{"issue", "show"}, needsRepo: true}),
596 pass("comment", passOpts{server: []string{"issue", "comment"}, needsRepo: true, stdinOK: true, editor: "comment"}),
597 pass("close", passOpts{server: []string{"issue", "close"}, needsRepo: true}),
598 pass("reopen", passOpts{server: []string{"issue", "reopen"}, needsRepo: true}),
599 pass("label", passOpts{server: []string{"issue", "label"}, needsRepo: true}),
600 pass("assign", passOpts{server: []string{"issue", "assign"}, needsRepo: true}),
601 pass("edit", passOpts{server: []string{"issue", "edit"}, needsRepo: true, stdinOK: true}),
602 pass("milestone", passOpts{server: []string{"issue", "milestone"}, needsRepo: true}),
603 pass("templates", passOpts{server: []string{"issue", "templates"}, needsRepo: true}),
556604 )
557605}
558606
559607func releaseCmd() *cobra.Command {
560608 return group("release", "tag-anchored releases with notes and assets",
561 pass("create", "create a release on a pushed tag: <tag> [--title <t>] [--notes|--file -|$EDITOR]",
562 passOpts{server: []string{"release", "create"}, needsRepo: true, stdinOK: true, editor: "release"}),
563 pass("edit", "update title and notes: <tag> [--title <t>] [--notes|--file -]",
564 passOpts{server: []string{"release", "edit"}, needsRepo: true, stdinOK: true}),
565 pass("list", "releases: <owner/name> [--limit <n>] [--cursor <c>]", passOpts{server: []string{"release", "list"}, needsRepo: true}),
566 pass("show", "show a release with assets: <tag>", passOpts{server: []string{"release", "show"}, needsRepo: true}),
567 pass("delete", "delete a release and its assets: <tag> --yes", passOpts{server: []string{"release", "delete"}, needsRepo: true}),
609 pass("create", passOpts{server: []string{"release", "create"}, needsRepo: true, stdinOK: true, editor: "release"}),
610 pass("edit", passOpts{server: []string{"release", "edit"}, needsRepo: true, stdinOK: true}),
611 pass("list", passOpts{server: []string{"release", "list"}, needsRepo: true}),
612 pass("show", passOpts{server: []string{"release", "show"}, needsRepo: true}),
613 pass("delete", passOpts{server: []string{"release", "delete"}, needsRepo: true}),
568614 group("asset", "binary assets on a release",
569 pass("add", "upload from stdin: <tag> <filename> < file", passOpts{server: []string{"release", "asset", "add"}, needsRepo: true, alwaysStdin: true, stdinWhat: "the asset's bytes"}),
570 pass("get", "download to stdout: <tag> <filename> > file", passOpts{server: []string{"release", "asset", "get"}, needsRepo: true}),
571 pass("remove", "remove an asset: <tag> <filename>", passOpts{server: []string{"release", "asset", "remove"}, needsRepo: true}),
615 pass("add", passOpts{server: []string{"release", "asset", "add"}, needsRepo: true, alwaysStdin: true, stdinWhat: "the asset's bytes"}),
616 pass("get", passOpts{server: []string{"release", "asset", "get"}, needsRepo: true}),
617 pass("remove", passOpts{server: []string{"release", "asset", "remove"}, needsRepo: true}),
572618 ),
573619 )
574620}
575621
576622func milestoneCmd() *cobra.Command {
577623 return group("milestone", "group issues and MRs toward a release",
578 pass("create", "create a milestone: <title> [--description <d>] [--due YYYY-MM-DD]",
579 passOpts{server: []string{"milestone", "create"}, needsRepo: true}),
580 pass("list", "list milestones with progress [--state open|closed|all]",
581 passOpts{server: []string{"milestone", "list"}, needsRepo: true}),
582 pass("close", "close a milestone: <title>", passOpts{server: []string{"milestone", "close"}, needsRepo: true}),
583 pass("reopen", "reopen a milestone: <title>", passOpts{server: []string{"milestone", "reopen"}, needsRepo: true}),
624 pass("create", passOpts{server: []string{"milestone", "create"}, needsRepo: true}),
625 pass("list", passOpts{server: []string{"milestone", "list"}, needsRepo: true}),
626 pass("close", passOpts{server: []string{"milestone", "close"}, needsRepo: true}),
627 pass("reopen", passOpts{server: []string{"milestone", "reopen"}, needsRepo: true}),
584628 )
585629}
586630
587631func mrCmd() *cobra.Command {
588 review := pass("review", "submit a review: --approve|--request-changes|--comment, or --discard a pending batch", passOpts{server: []string{"mr", "review"}, needsRepo: true})
589 review.AddCommand(pass("request", "ask specific people for review: [--add <u>]... [--remove <u>]...", passOpts{server: []string{"mr", "review", "request"}, needsRepo: true}))
632 review := pass("review", passOpts{server: []string{"mr", "review"}, needsRepo: true})
633 review.AddCommand(pass("request", passOpts{server: []string{"mr", "review", "request"}, needsRepo: true}))
590634 return group("mr", "merge requests",
591 pass("create", "open a merge request: --source <branch> --target <branch> --title <t>",
592 passOpts{server: []string{"mr", "create"}, needsRepo: true, stdinOK: true, editor: "merge request", inferSource: true}),
593 pass("list", "list merge requests [--state ...] [--label l] [--author u] [--milestone m|none] [--limit n] [--cursor c]", passOpts{server: []string{"mr", "list"}, needsRepo: true}),
594 pass("show", "show a merge request", passOpts{server: []string{"mr", "show"}, needsRepo: true}),
595 pass("diff", "show the diff", passOpts{server: []string{"mr", "diff"}, needsRepo: true}),
635 pass("create", passOpts{server: []string{"mr", "create"}, needsRepo: true, stdinOK: true, editor: "merge request", inferSource: true}),
636 pass("list", passOpts{server: []string{"mr", "list"}, needsRepo: true}),
637 pass("show", passOpts{server: []string{"mr", "show"}, needsRepo: true}),
638 pass("diff", passOpts{server: []string{"mr", "diff"}, needsRepo: true}),
596639 local("checkout", "fetch and check out the MR head locally: gitbay mr checkout <n>", cmdMRCheckout),
597640 local("rebase", "replay the MR's branch onto its target and re-push: gitbay mr rebase <n>", cmdMRRebase),
598 pass("comment", "comment on a merge request", passOpts{server: []string{"mr", "comment"}, needsRepo: true, stdinOK: true, editor: "comment"}),
599 pass("diff-comment", "comment on a diff line: --path <f> --line <l> [--old] [--pending] [--reply <id>]", passOpts{server: []string{"mr", "diff-comment"}, needsRepo: true, stdinOK: true, editor: "comment"}),
600 pass("threads", "review threads on an MR", passOpts{server: []string{"mr", "threads"}, needsRepo: true}),
601 pass("resolve", "resolve a review thread: <n> <thread-id>", passOpts{server: []string{"mr", "resolve"}, needsRepo: true}),
602 pass("unresolve", "reopen a review thread: <n> <thread-id>", passOpts{server: []string{"mr", "unresolve"}, needsRepo: true}),
641 pass("comment", passOpts{server: []string{"mr", "comment"}, needsRepo: true, stdinOK: true, editor: "comment"}),
642 pass("diff-comment", passOpts{server: []string{"mr", "diff-comment"}, needsRepo: true, stdinOK: true, editor: "comment"}),
643 pass("threads", passOpts{server: []string{"mr", "threads"}, needsRepo: true}),
644 pass("resolve", passOpts{server: []string{"mr", "resolve"}, needsRepo: true}),
645 pass("unresolve", passOpts{server: []string{"mr", "unresolve"}, needsRepo: true}),
603646 review,
604 pass("merge", "merge: [--strategy ff|merge|squash|rebase]", passOpts{server: []string{"mr", "merge"}, needsRepo: true}),
605 pass("close", "close without merging", passOpts{server: []string{"mr", "close"}, needsRepo: true}),
606 pass("revisions", "the heads this merge request has had", passOpts{server: []string{"mr", "revisions"}, needsRepo: true}),
607 pass("range-diff", "what changed between two revisions: [--from <sha>] [--to <sha>]", passOpts{server: []string{"mr", "range-diff"}, needsRepo: true}),
608 pass("draft", "mark as work in progress", passOpts{server: []string{"mr", "draft"}, needsRepo: true}),
609 pass("ready", "take the draft mark off, so it can merge", passOpts{server: []string{"mr", "ready"}, needsRepo: true}),
610 pass("edit", "edit title or body: <n> [--title <t>] [--body <b>|--file -]", passOpts{server: []string{"mr", "edit"}, needsRepo: true, stdinOK: true}),
611 pass("label", "add or remove labels: [--add <l>]... [--remove <l>]...", passOpts{server: []string{"mr", "label"}, needsRepo: true}),
612 pass("milestone", "set or clear the milestone: <n> <title|none>", passOpts{server: []string{"mr", "milestone"}, needsRepo: true}),
613 pass("retarget", "retarget onto another branch: <n> <branch>", passOpts{server: []string{"mr", "retarget"}, needsRepo: true}),
647 pass("merge", passOpts{server: []string{"mr", "merge"}, needsRepo: true}),
648 pass("close", passOpts{server: []string{"mr", "close"}, needsRepo: true}),
649 pass("revisions", passOpts{server: []string{"mr", "revisions"}, needsRepo: true}),
650 pass("range-diff", passOpts{server: []string{"mr", "range-diff"}, needsRepo: true}),
651 pass("draft", passOpts{server: []string{"mr", "draft"}, needsRepo: true}),
652 pass("ready", passOpts{server: []string{"mr", "ready"}, needsRepo: true}),
653 pass("edit", passOpts{server: []string{"mr", "edit"}, needsRepo: true, stdinOK: true}),
654 pass("label", passOpts{server: []string{"mr", "label"}, needsRepo: true}),
655 pass("milestone", passOpts{server: []string{"mr", "milestone"}, needsRepo: true}),
656 pass("retarget", passOpts{server: []string{"mr", "retarget"}, needsRepo: true}),
614657 )
615658}
616659
@@ -652,65 +695,64 @@ func usesTokenStdin(args []string) bool {
652695
653696func webCmd() *cobra.Command {
654697 return group("web", "browser session",
655 pass("login", "mint a one-time browser login URL over ssh", passOpts{server: []string{"web", "login"}}),
698 pass("login", passOpts{server: []string{"web", "login"}}),
656699 group("sessions", "your browser sessions",
657 pass("list", "list your browser sessions", passOpts{server: []string{"web", "sessions", "list"}}),
658 pass("revoke", "end a browser session: <id>|--all", passOpts{server: []string{"web", "sessions", "revoke"}}),
700 pass("list", passOpts{server: []string{"web", "sessions", "list"}}),
701 pass("revoke", passOpts{server: []string{"web", "sessions", "revoke"}}),
659702 ),
660703 group("theme", "the colour scheme the web UI uses for you",
661 pass("show", "show your colour scheme", passOpts{server: []string{"web", "theme", "show"}}),
662 pass("set", "follow the browser, or force one: system|light|dark", passOpts{server: []string{"web", "theme", "set"}}),
704 pass("show", passOpts{server: []string{"web", "theme", "show"}}),
705 pass("set", passOpts{server: []string{"web", "theme", "set"}}),
663706 ),
664707 )
665708}
666709
667710func webhookCmd() *cobra.Command {
668711 return group("webhook", "outbound event delivery",
669 pass("add", "add a webhook: <url> [--secret s] [--events k1,k2|*]", passOpts{server: []string{"webhook", "add"}, needsRepo: true}),
670 pass("list", "list webhooks", passOpts{server: []string{"webhook", "list"}, needsRepo: true}),
671 pass("remove", "remove a webhook: <id>", passOpts{server: []string{"webhook", "remove"}, needsRepo: true}),
672 pass("deliveries", "recent deliveries [--limit n]", passOpts{server: []string{"webhook", "deliveries"}, needsRepo: true}),
673 pass("redeliver", "requeue a delivery: <delivery-id>", passOpts{server: []string{"webhook", "redeliver"}, needsRepo: true}),
712 pass("add", passOpts{server: []string{"webhook", "add"}, needsRepo: true}),
713 pass("list", passOpts{server: []string{"webhook", "list"}, needsRepo: true}),
714 pass("remove", passOpts{server: []string{"webhook", "remove"}, needsRepo: true}),
715 pass("deliveries", passOpts{server: []string{"webhook", "deliveries"}, needsRepo: true}),
716 pass("redeliver", passOpts{server: []string{"webhook", "redeliver"}, needsRepo: true}),
674717 )
675718}
676719
677720func orgCmd() *cobra.Command {
678721 return group("org", "organizations",
679 pass("create", "create an organization", passOpts{server: []string{"org", "create"}}),
680 pass("list", "list organizations you belong to", passOpts{server: []string{"org", "list"}}),
681 pass("show", "show an organization and its members", passOpts{server: []string{"org", "show"}}),
682 pass("rename", "rename an organization: <old> <new>", passOpts{server: []string{"org", "rename"}}),
683 pass("delete", "delete an empty organization (--yes)", passOpts{server: []string{"org", "delete"}}),
684 pass("profile", "show or set an org profile: <org> [--description d] [--website url] [--link label|url]...",
685 passOpts{server: []string{"org", "profile"}}),
722 pass("create", passOpts{server: []string{"org", "create"}}),
723 pass("list", passOpts{server: []string{"org", "list"}}),
724 pass("show", passOpts{server: []string{"org", "show"}}),
725 pass("rename", passOpts{server: []string{"org", "rename"}}),
726 pass("delete", passOpts{server: []string{"org", "delete"}}),
727 pass("profile", passOpts{server: []string{"org", "profile"}}),
686728 group("members", "manage members",
687 pass("add", "add or update a member: <org> <user> [--role member|admin]", passOpts{server: []string{"org", "members", "add"}}),
688 pass("remove", "remove a member: <org> <user>", passOpts{server: []string{"org", "members", "remove"}}),
689 pass("list", "list members: <org>", passOpts{server: []string{"org", "members", "list"}}),
729 pass("add", passOpts{server: []string{"org", "members", "add"}}),
730 pass("remove", passOpts{server: []string{"org", "members", "remove"}}),
731 pass("list", passOpts{server: []string{"org", "members", "list"}}),
690732 ),
691733 group("label", "labels every org repository sees",
692 pass("set", "create an org label or set its colour: <org> <label> [--color rrggbb|'']", passOpts{server: []string{"org", "label", "set"}}),
693 pass("list", "list org labels with use across readable repositories: <org>", passOpts{server: []string{"org", "label", "list"}}),
694 pass("remove", "remove an org label everywhere: <org> <label>", passOpts{server: []string{"org", "label", "remove"}}),
734 pass("set", passOpts{server: []string{"org", "label", "set"}}),
735 pass("list", passOpts{server: []string{"org", "label", "list"}}),
736 pass("remove", passOpts{server: []string{"org", "label", "remove"}}),
695737 ),
696738 group("milestone", "milestones spanning an org's repositories",
697 pass("create", "create an org milestone: <org> <title> [--description d] [--due YYYY-MM-DD]", passOpts{server: []string{"org", "milestone", "create"}}),
698 pass("list", "list org milestones with progress: <org> [--state open|closed|all]", passOpts{server: []string{"org", "milestone", "list"}}),
699 pass("close", "close an org milestone: <org> <title>", passOpts{server: []string{"org", "milestone", "close"}}),
700 pass("reopen", "reopen an org milestone: <org> <title>", passOpts{server: []string{"org", "milestone", "reopen"}}),
739 pass("create", passOpts{server: []string{"org", "milestone", "create"}}),
740 pass("list", passOpts{server: []string{"org", "milestone", "list"}}),
741 pass("close", passOpts{server: []string{"org", "milestone", "close"}}),
742 pass("reopen", passOpts{server: []string{"org", "milestone", "reopen"}}),
701743 ),
702744 group("team", "scope repository access with teams",
703 pass("create", "create a team: <org> <team>", passOpts{server: []string{"org", "team", "create"}}),
704 pass("delete", "delete a team: <org> <team>", passOpts{server: []string{"org", "team", "delete"}}),
705 pass("list", "list teams: <org>", passOpts{server: []string{"org", "team", "list"}}),
706 pass("show", "show members and grants: <org> <team>", passOpts{server: []string{"org", "team", "show"}}),
707 pass("add", "add org members: <org> <team> <user>...", passOpts{server: []string{"org", "team", "add"}}),
708 pass("remove", "remove members: <org> <team> <user>...", passOpts{server: []string{"org", "team", "remove"}}),
709 pass("grant", "grant a repo role: <org> <team> <owner/name> read|write|admin", passOpts{server: []string{"org", "team", "grant"}}),
710 pass("revoke", "revoke a repo grant: <org> <team> <owner/name>", passOpts{server: []string{"org", "team", "revoke"}}),
745 pass("create", passOpts{server: []string{"org", "team", "create"}}),
746 pass("delete", passOpts{server: []string{"org", "team", "delete"}}),
747 pass("list", passOpts{server: []string{"org", "team", "list"}}),
748 pass("show", passOpts{server: []string{"org", "team", "show"}}),
749 pass("add", passOpts{server: []string{"org", "team", "add"}}),
750 pass("remove", passOpts{server: []string{"org", "team", "remove"}}),
751 pass("grant", passOpts{server: []string{"org", "team", "grant"}}),
752 pass("revoke", passOpts{server: []string{"org", "team", "revoke"}}),
711753 ),
712754 group("settings", "organization settings",
713 pass("members-role", "role plain membership implies: <org> write|read|none", passOpts{server: []string{"org", "settings", "members-role"}}),
755 pass("members-role", passOpts{server: []string{"org", "settings", "members-role"}}),
714756 ),
715757 )
716758}
@@ -724,7 +766,7 @@ func remoteCmd() *cobra.Command {
724766}
725767
726768func initCmd() *cobra.Command {
727 return local("init", "git init + repo create + set origin, in one step: gitbay init [name] [--private]", cmdInit)
769 return local("init [name] [--private]", "git init + repo create + set origin, in one step", cmdInit)
728770}
729771
730772// manCmd generates man pages; a CLI-first tool without man pages is not
@@ -759,7 +801,8 @@ func helpCmd(root *cobra.Command) *cobra.Command {
759801 DisableFlagParsing: true,
760802 RunE: func(cmd *cobra.Command, args []string) error {
761803 if len(args) == 0 {
762 return root.Help()
804 rootHelp(root)
805 return nil
763806 }
764807 t, err := resolveTarget()
765808 if err != nil {
cmd/gitbay/migrate.go +2 −2
@@ -18,8 +18,8 @@ func migrateCmd() *cobra.Command {
1818 var from string
1919 var fromPort int
2020 cmd := &cobra.Command{
21 Use: "migrate",
22 Short: "move your account here from another gitbay instance: gitbay migrate --from <host>",
21 Use: "migrate --from <host>",
22 Short: "move your account here from another gitbay instance",
2323 Long: `Migrate authenticates to the source instance with your own SSH key,
2424exports your account bundle (profile, emails, repos with settings,
2525issues, MRs, comments), replays it on this instance, then mirrors each
cmd/gitbay/summaries_gen.go added +238
@@ -0,0 +1,238 @@
1// Code generated by TestSummariesAreCurrent -update; DO NOT EDIT.
2
3package main
4
5var summaries = map[string]string{
6 "account export": "write your account bundle (profile, repos, issues, MRs) as JSON",
7 "account import-bundle": "replay an account bundle (see gitbay migrate)",
8 "admin email verify": "mark an address verified by admin assertion",
9 "admin invite": "issue a registration invite and mail its code",
10 "admin mr prune": "drop merged or closed MRs' head refs and the objects only they kept, e.g. after a history rewrite (instance admins; audited)",
11 "admin repo archive": "archive any repository (instance admins; audited)",
12 "admin repo delete": "delete any repository (instance admins; audited)",
13 "admin repo list": "list every repository with size and last push (instance admins)",
14 "admin repo unarchive": "unarchive any repository (instance admins; audited)",
15 "admin repo visibility": "set any repository's visibility (instance admins; audited)",
16 "admin runners forget": "alias of admin runners remove",
17 "admin runners remove": "drop a key's runner heartbeat row, e.g. one that polled once by mistake (instance admins)",
18 "admin runners": "the build queue and runner accounts: last poll, scope, the build each holds (instance admins)",
19 "admin stats": "instance statistics: counts and per-repository disk usage",
20 "admin user create": "create an account, optionally with a key and a verified address (instance admins)",
21 "admin user delete": "delete an account that anchors nothing (keys, emails and sessions go with it)",
22 "admin user demote": "remove instance admin from an account (never the last one)",
23 "admin user disable": "suspend an account: SSH, web sessions and API tokens refused until re-enabled",
24 "admin user enable": "restore a suspended account",
25 "admin user limits": "show or set an account's repository and storage caps (instance admins)",
26 "admin user list": "list accounts (instance admins)",
27 "admin user promote": "make an account an instance admin",
28 "admin user show": "show an account: keys, emails, orgs, tokens, sessions (instance admins)",
29 "audit": "instance audit log (admins)",
30 "build cancel": "withdraw a queued build before a runner claims it",
31 "build jobs": "list the jobs a trigger can name",
32 "build list": "list recent builds",
33 "build log": "print a build's log, or follow it until the build ends",
34 "build show": "show one build",
35 "build trigger": "queue a job now (scheduled or not)",
36 "dashboard": "one read for the account dashboard: review queue, assigned and open work, pins, activity, builds",
37 "email add": "add an address and mail a verification code",
38 "email list": "list the addresses on your account",
39 "email primary": "make a verified address the primary",
40 "email remove": "remove an address; not the primary, nor the last verified one",
41 "email verify": "confirm a verification code",
42 "explore": "list public repositories",
43 "feed": "activity on repositories you can reach",
44 "help": "list available commands",
45 "issue assign": "assignees",
46 "issue close": "close an issue",
47 "issue comment": "comment",
48 "issue create": "open an issue",
49 "issue edit": "edit title or body",
50 "issue label": "labels",
51 "issue list": "list issues",
52 "issue milestone": "set or clear an issue's milestone",
53 "issue reopen": "reopen an issue",
54 "issue show": "show an issue with comments",
55 "issue templates": "list issue templates (.gitbay/issue-template*.md)",
56 "keys add": "register an SSH public key (authorized_keys format)",
57 "keys label": "name a key; an empty label clears it",
58 "keys list": "list registered SSH keys",
59 "keys remove": "remove an SSH key by fingerprint",
60 "label list": "list a repository's labels with colour and use",
61 "label remove": "remove a label from the repository and from every issue and merge request",
62 "label set": "create a label or set its colour",
63 "milestone close": "close a milestone",
64 "milestone create": "create a milestone",
65 "milestone list": "list milestones with progress",
66 "milestone reopen": "reopen a milestone",
67 "mr close": "close without merging",
68 "mr comment": "comment",
69 "mr create": "open a merge request",
70 "mr diff": "show the diff",
71 "mr diff-comment": "comment on a diff line",
72 "mr draft": "mark a merge request as work in progress",
73 "mr edit": "edit title or body",
74 "mr label": "labels",
75 "mr list": "list merge requests",
76 "mr merge": "merge",
77 "mr milestone": "set or clear an MR's milestone",
78 "mr range-diff": "what changed between two revisions of a merge request",
79 "mr ready": "take the draft mark off, so it can merge",
80 "mr resolve": "resolve a review thread",
81 "mr retarget": "retarget onto another branch",
82 "mr review request": "ask specific people for a review",
83 "mr review": "review",
84 "mr revisions": "the heads a merge request has had",
85 "mr show": "show a merge request",
86 "mr threads": "review threads on an MR",
87 "mr unresolve": "reopen a review thread",
88 "notifications device add": "register an Apple device for push, token on stdin",
89 "notifications device list": "your registered devices",
90 "notifications device remove": "deregister a device",
91 "notifications list": "your notification inbox, newest first",
92 "notifications read": "mark notifications read",
93 "notifications settings mail": "activity by mail as well as the inbox (login links are unaffected)",
94 "notifications settings push": "activity on your registered devices as well as the inbox",
95 "notifications settings show": "your notification preferences",
96 "notifications settings watch": "every issue and merge request on repositories you can write to",
97 "org create": "create an organization (you become its first admin)",
98 "org delete": "delete an empty organization",
99 "org label list": "list an org's labels with use across the repositories you can read",
100 "org label remove": "remove an org label from the org and from every issue under it",
101 "org label set": "create an org label every org repository sees, or set its colour; folds in same-named repo labels",
102 "org list": "list organizations you belong to",
103 "org members add": "add or update a member",
104 "org members list": "list members",
105 "org members remove": "remove a member",
106 "org milestone close": "close an org milestone",
107 "org milestone create": "create an org milestone spanning every org repository; folds in same-titled repo milestones",
108 "org milestone list": "list an org's milestones with progress across the repositories you can read",
109 "org milestone reopen": "reopen an org milestone",
110 "org profile": "show or set an org's profile",
111 "org rename": "rename an organization",
112 "org settings members-role": "role plain membership implies on every org repo",
113 "org show": "show an organization and its members",
114 "org team add": "add org members to a team",
115 "org team create": "create a team",
116 "org team delete": "delete a team (its grants with it)",
117 "org team grant": "grant a team a role on an org repo",
118 "org team list": "list an org's teams",
119 "org team remove": "remove members from a team",
120 "org team revoke": "revoke a team's grant",
121 "org team show": "show a team's members and grants",
122 "pgp add": "register an OpenPGP public key (armored)",
123 "pgp list": "list registered OpenPGP keys",
124 "pgp remove": "remove an OpenPGP key by fingerprint",
125 "profile set": "set your profile",
126 "profile show": "show a user's or org's profile",
127 "register": "create an account (only meaningful for unregistered keys)",
128 "release asset add": "upload an asset from stdin",
129 "release asset get": "write an asset to stdout",
130 "release asset remove": "remove an asset",
131 "release create": "create a release on a tag",
132 "release delete": "delete a release and its assets",
133 "release edit": "update a release's title and notes",
134 "release list": "list releases",
135 "release show": "show a release with assets",
136 "repo access grant": "grant access",
137 "repo access list": "list who can reach the repository, with the role and where it comes from",
138 "repo access revoke": "revoke access",
139 "repo archive": "archive a repository (read-only: pushes and issue/MR writes refused)",
140 "repo blame": "attribute lines to commits",
141 "repo bookmark": "bookmark a repository to come back to",
142 "repo bookmarks": "list the repositories you have bookmarked",
143 "repo cat": "read a file",
144 "repo commit": "show one commit with its patch",
145 "repo commit-file": "write a file and commit it",
146 "repo create": "create a repository",
147 "repo delete": "delete a repository",
148 "repo deploy-key add": "bind a read-only (or --rw) key to one repository",
149 "repo deploy-key list": "list deploy keys",
150 "repo deploy-key remove": "remove a deploy key",
151 "repo deps disable": "stop checking dependencies",
152 "repo deps enable": "check dependencies for updates",
153 "repo deps status": "show dependency check state",
154 "repo diff": "the patch between two refs, from their merge base",
155 "repo domain add": "claim a custom pages domain (verify with a DNS TXT record)",
156 "repo domain list": "list custom pages domains",
157 "repo domain remove": "remove a custom pages domain",
158 "repo domain verify": "check the DNS challenge and activate a claim",
159 "repo download": "write a tar.gz of a ref to stdout",
160 "repo fork": "fork a repository under your account",
161 "repo grep": "search file contents",
162 "repo import": "server-side mirror of a foreign repository",
163 "repo import-issues": "import issue and PR history from GitHub or Forgejo",
164 "repo list": "list repositories you own or can access",
165 "repo log": "commit log with signature states",
166 "repo mirror add": "mirror to or from a remote",
167 "repo mirror list": "list mirrors with sync status",
168 "repo mirror remove": "remove a mirror",
169 "repo mirror sync": "schedule an immediate sync",
170 "repo mute": "mute a repository, including work you are part of",
171 "repo pin": "pin a repository to your dashboard",
172 "repo refs": "list branches and tags",
173 "repo rename": "rename a repository",
174 "repo runner add": "attach a runner's public key to a repository",
175 "repo runner list": "list the runners attached to a repository",
176 "repo runner remove": "detach a runner from a repository",
177 "repo search": "find repositories by name, description, or topic",
178 "repo secret list": "list build secret names",
179 "repo secret remove": "remove a build secret",
180 "repo secret set": "set a build secret",
181 "repo settings default-branch": "set the default branch",
182 "repo settings description": "set the repository description",
183 "repo settings git-daemon": "expose over git://",
184 "repo settings protect": "protect a branch",
185 "repo settings protect-tag": "protect tags matching a glob (created once, never moved or deleted)",
186 "repo settings require-approvals": "require N fresh approvals to merge",
187 "repo settings require-checks": "gate merges on green statuses",
188 "repo settings require-codeowners": "require an owner's approval for every file CODEOWNERS covers",
189 "repo settings require-mr": "protected branches take changes through merge requests only",
190 "repo settings require-resolved": "require all review threads resolved to merge",
191 "repo settings require-signed": "require verified commit signatures",
192 "repo settings show": "show settings",
193 "repo settings unprotect": "unprotect a branch",
194 "repo settings unprotect-tag": "drop a protected-tag glob",
195 "repo settings visibility": "set repository visibility",
196 "repo settings website": "set the repository website",
197 "repo show": "show repository details",
198 "repo topics add": "add topics",
199 "repo topics remove": "remove topics",
200 "repo topics": "list topics",
201 "repo transfer": "move a repository to another owner",
202 "repo tree": "list a directory",
203 "repo unarchive": "unarchive a repository",
204 "repo unbookmark": "remove a bookmark",
205 "repo unpin": "unpin a repository",
206 "repo unwatch": "back to the default: only work you are part of",
207 "repo watch": "hear about all activity on a repository",
208 "runner done": "finish a build",
209 "runner log": "append a build's log from stdin",
210 "runner next": "claim the oldest pending build this key may run (runner protocol)",
211 "search": "find repositories, issues and merge requests across the instance",
212 "snippet create": "create a snippet from one file on stdin",
213 "snippet delete": "delete a snippet and its files",
214 "snippet edit": "change a snippet's description or visibility",
215 "snippet file get": "write a snippet file to stdout",
216 "snippet file remove": "remove a file from a snippet",
217 "snippet file set": "add a file to a snippet, or replace one, from stdin",
218 "snippet list": "list your snippets, or an owner's public ones",
219 "snippet show": "show a snippet's metadata and files",
220 "status list": "statuses on a commit",
221 "status set": "report a commit status (CI)",
222 "token create": "mint an API token (shown once)",
223 "token list": "list API tokens",
224 "token revoke": "revoke an API token by name",
225 "web login": "mint a one-time browser login URL",
226 "web sessions list": "list your browser sessions",
227 "web sessions revoke": "end a browser session, or all of them",
228 "web theme set": "follow the browser's scheme, or force light or dark",
229 "web theme show": "the colour scheme the web UI uses for you",
230 "webhook add": "add a webhook",
231 "webhook deliveries": "recent deliveries",
232 "webhook list": "list webhooks",
233 "webhook redeliver": "queue a delivery again",
234 "webhook remove": "remove a webhook",
235 "whoami": "show the authenticated account",
236 "wiki list": "list a repository's wiki pages",
237 "wiki show": "print a wiki page",
238}
cmd/gitbay/summaries_test.go added +62
@@ -0,0 +1,62 @@
1package main
2
3import (
4 "flag"
5 "fmt"
6 "os"
7 "slices"
8 "strings"
9 "testing"
10
11 "gitbay.org/gitbay/internal/control"
12)
13
14var updateSummaries = flag.Bool("update", false, "rewrite summaries_gen.go")
15
16// summaries_gen.go is the registry's one-line summaries, so the CLI's
17// command list and completions say what help says.
18func TestSummariesAreCurrent(t *testing.T) {
19 var b strings.Builder
20 b.WriteString("// Code generated by TestSummariesAreCurrent -update; DO NOT EDIT.\n\npackage main\n\nvar summaries = map[string]string{\n")
21 var lines []string
22 for _, cmd := range control.Commands() {
23 lines = append(lines, fmt.Sprintf("\t%q: %q,\n", strings.Join(cmd.Path, " "), cmd.Summary))
24 }
25 slices.Sort(lines)
26 b.WriteString(strings.Join(lines, ""))
27 b.WriteString("}\n")
28 if *updateSummaries {
29 os.WriteFile("summaries_gen.go", []byte(b.String()), 0o644)
30 }
31 got, _ := os.ReadFile("summaries_gen.go")
32 if string(got) != b.String() {
33 t.Fatal("summaries_gen.go is stale: go test ./cmd/gitbay -run TestSummariesAreCurrent -update")
34 }
35}
36
37func TestRootSectionsCoverEveryCommand(t *testing.T) {
38 seen := map[string]int{}
39 for _, s := range rootSections {
40 for _, n := range s.names {
41 seen[n]++
42 }
43 }
44 for _, c := range newRoot().Commands() {
45 name := c.Name()
46 if name == "help" || name == "completion" {
47 continue
48 }
49 if seen[name] != 1 {
50 t.Errorf("%s is in %d root sections", name, seen[name])
51 }
52 }
53}
54
55func TestGroupsSayWhatTheServerSays(t *testing.T) {
56 nouns := control.NounSummaries()
57 for _, c := range newRoot().Commands() {
58 if s, ok := nouns[c.Name()]; ok && c.Short != s {
59 t.Errorf("%s: CLI says %q, server says %q", c.Name(), c.Short, s)
60 }
61 }
62}