| @@ -12,6 +12,7 @@ import ( |
| 12 | 12 | |
| 13 | 13 | "github.com/spf13/cobra" |
| 14 | 14 | "github.com/spf13/cobra/doc" |
| 15 | "golang.org/x/term" |
| 15 | 16 | |
| 16 | 17 | "gitbay.org/gitbay/internal/protocol" |
| 17 | 18 | ) |
| @@ -38,68 +39,55 @@ func newRoot() *cobra.Command { |
| 38 | 39 | root.AddCommand( |
| 39 | 40 | authCmd(), |
| 40 | 41 | 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}), |
| 44 | 45 | ), |
| 45 | 46 | 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}), |
| 48 | 49 | ), |
| 49 | 50 | 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}), |
| 56 | 57 | ), |
| 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"), |
| 65 | 62 | 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"}}), |
| 70 | 65 | 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"}}), |
| 75 | 70 | ), |
| 76 | 71 | 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"}}), |
| 83 | 75 | ), |
| 84 | 76 | ), |
| 85 | 77 | 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}), |
| 88 | 80 | ), |
| 89 | 81 | 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"}}), |
| 98 | 87 | 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"}}), |
| 103 | 91 | ), |
| 104 | 92 | ), |
| 105 | 93 | repoCmd(), |
| @@ -111,52 +99,106 @@ func newRoot() *cobra.Command { |
| 111 | 99 | webCmd(), |
| 112 | 100 | orgCmd(), |
| 113 | 101 | 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"}}), |
| 117 | 104 | ), |
| 118 | 105 | webhookCmd(), |
| 119 | 106 | remoteCmd(), |
| 120 | 107 | 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"}}), |
| 124 | 110 | group("admin", "instance administration (admins)", |
| 125 | 111 | 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"}}), |
| 135 | 121 | ), |
| 136 | 122 | 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"}}), |
| 138 | 124 | ), |
| 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"}})), |
| 144 | 130 | 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"}}), |
| 150 | 136 | ), |
| 151 | 137 | 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"}}), |
| 153 | 139 | ), |
| 154 | 140 | ), |
| 155 | 141 | manCmd(root), |
| 156 | 142 | ) |
| 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 | }) |
| 157 | 151 | return root |
| 158 | 152 | } |
| 159 | 153 | |
| 154 | // rootSection is one heading in gitbay --help: a title and the root |
| 155 | // commands that belong under it. |
| 156 | type rootSection struct { |
| 157 | title string |
| 158 | names []string |
| 159 | } |
| 160 | |
| 161 | var 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. |
| 169 | func 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 | |
| 160 | 202 | // serverPath is the annotation key holding a passthrough command's |
| 161 | 203 | // server-side path, so the tree can be checked against the registry. |
| 162 | 204 | const serverPath = "gitbay.server_path" |
| @@ -197,10 +239,10 @@ func (o passOpts) stdinModeName() string { |
| 197 | 239 | return "none" |
| 198 | 240 | } |
| 199 | 241 | |
| 200 | | func pass(use, short string, o passOpts) *cobra.Command { |
| 242 | func pass(use string, o passOpts) *cobra.Command { |
| 201 | 243 | return &cobra.Command{ |
| 202 | 244 | Use: use, |
| 203 | | Short: short, |
| 245 | Short: summaries[strings.Join(o.server, " ")], |
| 204 | 246 | Annotations: map[string]string{ |
| 205 | 247 | serverPath: strings.Join(o.server, " "), |
| 206 | 248 | stdinMode: o.stdinModeName(), |
| @@ -222,6 +264,17 @@ func pass(use, short string, o passOpts) *cobra.Command { |
| 222 | 264 | } |
| 223 | 265 | } |
| 224 | 266 | |
| 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. |
| 273 | func withShort(cmd *cobra.Command, short string) *cobra.Command { |
| 274 | cmd.Short = short |
| 275 | return cmd |
| 276 | } |
| 277 | |
| 225 | 278 | // runServerHelp prints the registry's usage for one command. |
| 226 | 279 | func runServerHelp(o passOpts) int { |
| 227 | 280 | t, err := resolveTarget() |
| @@ -335,12 +388,18 @@ func group(use, short string, subs ...*cobra.Command) *cobra.Command { |
| 335 | 388 | } |
| 336 | 389 | |
| 337 | 390 | // 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. |
| 339 | 395 | func serverHelp(prefix string) bool { |
| 340 | 396 | t, err := resolveTarget() |
| 341 | 397 | if err != nil { |
| 342 | 398 | return false |
| 343 | 399 | } |
| 400 | if term.IsTerminal(int(os.Stdout.Fd())) { |
| 401 | return runSSH(t, []string{"help", prefix}, strings.NewReader("")) == 0 |
| 402 | } |
| 344 | 403 | out, code := sshCapture(t, []string{"help", prefix}) |
| 345 | 404 | if code != 0 || out == "" { |
| 346 | 405 | return false |
| @@ -368,8 +427,7 @@ func local(use, short string, fn func(args []string) int) *cobra.Command { |
| 368 | 427 | } |
| 369 | 428 | |
| 370 | 429 | func 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"}) |
| 373 | 431 | // keys add always reads stdin on the server; wire it through directly. |
| 374 | 432 | keysAdd.RunE = func(cmd *cobra.Command, args []string) error { |
| 375 | 433 | t, err := resolveTarget() |
| @@ -405,212 +463,197 @@ func authCmd() *cobra.Command { |
| 405 | 463 | }, |
| 406 | 464 | } |
| 407 | 465 | 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"}}), |
| 411 | 469 | ) |
| 412 | 470 | 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"}}), |
| 415 | 472 | tokens, |
| 416 | | pass("whoami", "show the authenticated account", passOpts{server: []string{"whoami"}}), |
| 473 | pass("whoami", passOpts{server: []string{"whoami"}}), |
| 417 | 474 | 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"}}), |
| 419 | 476 | 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"}}), |
| 422 | 479 | ), |
| 423 | 480 | 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"}}), |
| 429 | 486 | ), |
| 430 | 487 | 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"}}), |
| 432 | 489 | pgpAdd, |
| 433 | | pass("remove", "remove a PGP key by fingerprint", passOpts{server: []string{"pgp", "remove"}}), |
| 490 | pass("remove", passOpts{server: []string{"pgp", "remove"}}), |
| 434 | 491 | ), |
| 435 | 492 | ) |
| 436 | 493 | } |
| 437 | 494 | |
| 438 | 495 | func repoCmd() *cobra.Command { |
| 439 | 496 | 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}), |
| 473 | 525 | local("clone", "clone via ssh: gitbay repo clone <owner/name> [dir]", cmdRepoClone), |
| 474 | 526 | 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}), |
| 477 | 528 | 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}), |
| 481 | 532 | ), |
| 482 | 533 | 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}), |
| 486 | 537 | ), |
| 487 | 538 | 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}), |
| 493 | 543 | ), |
| 494 | 544 | 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}), |
| 498 | 548 | ), |
| 499 | 549 | 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}), |
| 503 | 553 | ), |
| 504 | 554 | 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}), |
| 509 | 559 | ), |
| 510 | 560 | 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}), |
| 514 | 564 | ), |
| 515 | 565 | 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}), |
| 519 | 569 | ), |
| 520 | 570 | 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}), |
| 537 | 587 | ), |
| 538 | 588 | ) |
| 539 | 589 | } |
| 540 | 590 | |
| 541 | 591 | func issueCmd() *cobra.Command { |
| 542 | 592 | 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}), |
| 556 | 604 | ) |
| 557 | 605 | } |
| 558 | 606 | |
| 559 | 607 | func releaseCmd() *cobra.Command { |
| 560 | 608 | 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}), |
| 568 | 614 | 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}), |
| 572 | 618 | ), |
| 573 | 619 | ) |
| 574 | 620 | } |
| 575 | 621 | |
| 576 | 622 | func milestoneCmd() *cobra.Command { |
| 577 | 623 | 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}), |
| 584 | 628 | ) |
| 585 | 629 | } |
| 586 | 630 | |
| 587 | 631 | func 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})) |
| 590 | 634 | 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}), |
| 596 | 639 | local("checkout", "fetch and check out the MR head locally: gitbay mr checkout <n>", cmdMRCheckout), |
| 597 | 640 | 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}), |
| 603 | 646 | 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}), |
| 614 | 657 | ) |
| 615 | 658 | } |
| 616 | 659 | |
| @@ -652,65 +695,64 @@ func usesTokenStdin(args []string) bool { |
| 652 | 695 | |
| 653 | 696 | func webCmd() *cobra.Command { |
| 654 | 697 | 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"}}), |
| 656 | 699 | 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"}}), |
| 659 | 702 | ), |
| 660 | 703 | 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"}}), |
| 663 | 706 | ), |
| 664 | 707 | ) |
| 665 | 708 | } |
| 666 | 709 | |
| 667 | 710 | func webhookCmd() *cobra.Command { |
| 668 | 711 | 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}), |
| 674 | 717 | ) |
| 675 | 718 | } |
| 676 | 719 | |
| 677 | 720 | func orgCmd() *cobra.Command { |
| 678 | 721 | 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"}}), |
| 686 | 728 | 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"}}), |
| 690 | 732 | ), |
| 691 | 733 | 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"}}), |
| 695 | 737 | ), |
| 696 | 738 | 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"}}), |
| 701 | 743 | ), |
| 702 | 744 | 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"}}), |
| 711 | 753 | ), |
| 712 | 754 | 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"}}), |
| 714 | 756 | ), |
| 715 | 757 | ) |
| 716 | 758 | } |
| @@ -724,7 +766,7 @@ func remoteCmd() *cobra.Command { |
| 724 | 766 | } |
| 725 | 767 | |
| 726 | 768 | func 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) |
| 728 | 770 | } |
| 729 | 771 | |
| 730 | 772 | // manCmd generates man pages; a CLI-first tool without man pages is not |
| @@ -759,7 +801,8 @@ func helpCmd(root *cobra.Command) *cobra.Command { |
| 759 | 801 | DisableFlagParsing: true, |
| 760 | 802 | RunE: func(cmd *cobra.Command, args []string) error { |
| 761 | 803 | if len(args) == 0 { |
| 762 | | return root.Help() |
| 804 | rootHelp(root) |
| 805 | return nil |
| 763 | 806 | } |
| 764 | 807 | t, err := resolveTarget() |
| 765 | 808 | if err != nil { |