CLI output sweep (#183) !388
45 files changed, +346 −276
Layout: unified · split
.gitbay/wiki/Admin.org +1 −1
| @@ -434,7 +434,7 @@ podman. A runner without it builds trusted commits only. | |||
| 434 | that has polled as a runner: the account, the key's fingerprint, when it | 434 | that has polled as a runner: the account, the key's fingerprint, when it |
| 435 | last polled, the repositories it may claim — its attachments for a runner | 435 | last polled, the repositories it may claim — its attachments for a runner |
| 436 | key, the =-repos= it asked for or =any= for an admin key — and the build | 436 | key, the =-repos= it asked for or =any= for an admin key — and the build |
| 437 | it holds; =admin runners forget <fingerprint>= drops the row for a key | 437 | it holds; =admin runners remove <fingerprint>= (=forget= until the next release) drops the row for a key |
| 438 | that polled by mistake, the key itself untouched. =admin runners= also | 438 | that polled by mistake, the key itself untouched. =admin runners= also |
| 439 | heads the list with the queue: builds | 439 | heads the list with the queue: builds |
| 440 | pending now, and over the last day how many were claimed, how long they | 440 | pending now, and over the last day how many were claimed, how long they |
.gitbay/wiki/Users.org +28
| @@ -656,6 +656,34 @@ someone else. | |||
| 656 | You are never mailed about your own actions, and only verified primary | 656 | You are never mailed about your own actions, and only verified primary |
| 657 | addresses receive anything. Delivery retries on relay failure. | 657 | addresses receive anything. Delivery retries on relay failure. |
| 658 | 658 | ||
| 659 | * Output rules | ||
| 660 | |||
| 661 | =--json= is the contract; the plain output is for a person at a | ||
| 662 | terminal, and follows these rules so every noun reads the same way. | ||
| 663 | |||
| 664 | - A list command prints one row per item, tab-separated, no header. | ||
| 665 | Columns run identifier, state, then description; a trailing column | ||
| 666 | may carry a word (=due 2027-01-01=, =via team=). The =gitbay= CLI pads | ||
| 667 | the tabs into aligned columns when stdout is a terminal and leaves | ||
| 668 | them as tabs when piped, so =cut -f= sees the same bytes stock ssh | ||
| 669 | prints. Under =--json= nothing is touched. | ||
| 670 | - An empty list prints nothing on stdout and =nothing to list= on | ||
| 671 | stderr. | ||
| 672 | - A mutation prints one line: verb, object, identifier | ||
| 673 | (=created krz/gitbay#7=). A second line appears only for something | ||
| 674 | to copy: a URL, a token shown once. | ||
| 675 | - A bad invocation prints =usage:= and the command's registered usage, | ||
| 676 | the same text =help <noun>= shows. Exit 2. | ||
| 677 | - A refusal says who may and what to do instead: =only admins of acme | ||
| 678 | can manage teams; ask one to add you=. Exit 4. A thing that does not | ||
| 679 | exist, or that you may not know exists, is exit 3. | ||
| 680 | - stdout is the result; stderr is everything else: progress, a note | ||
| 681 | that output was truncated, errors. | ||
| 682 | - Verbs: =create= and =delete= for things with their own identity | ||
| 683 | (repository, issue, release, team), =add= and =remove= for attaching | ||
| 684 | something to them (a key, a member, a label on an issue), =set= for | ||
| 685 | a value, =revoke= for a credential, =show= and =list= for reads. | ||
| 686 | |||
| 659 | * Scripting | 687 | * Scripting |
| 660 | 688 | ||
| 661 | Every read command takes =--json= and emits one envelope: | 689 | Every read command takes =--json= and emits one envelope: |
CHANGELOG.org +16
| @@ -4,6 +4,22 @@ Versioning follows semver from v0.1.0. Database migrations run | |||
| 4 | automatically on daemon start; upgrade notes appear per release when | 4 | automatically on daemon start; upgrade notes appear per release when |
| 5 | anything beyond "replace the binary and restart" is needed. | 5 | anything beyond "replace the binary and restart" is needed. |
| 6 | 6 | ||
| 7 | * v1.22.0 — unreleased | ||
| 8 | |||
| 9 | The CLI output sweep (#183). The rules are on the wiki's Users page, | ||
| 10 | under "Output rules". | ||
| 11 | |||
| 12 | - A bad invocation prints the command's registered usage, the one | ||
| 13 | source of it; the hand-written copies are gone. | ||
| 14 | - The =gitbay= CLI pads list rows into aligned columns at a terminal | ||
| 15 | and leaves the tabs alone when piped. | ||
| 16 | - An empty list prints nothing on stdout and =nothing to list= on | ||
| 17 | stderr. | ||
| 18 | - Every refusal (exit 4) says what to do instead. | ||
| 19 | - =status list= and =repo topics= print rows. | ||
| 20 | - =admin runners remove=; =forget= stays as an alias until the next | ||
| 21 | release. | ||
| 22 | |||
| 7 | * v1.21.0 — 2026-09-12 | 23 | * v1.21.0 — 2026-09-12 |
| 8 | 24 | ||
| 9 | The web UI/UX sweep (#182). | 25 | The web UI/UX sweep (#182). |
cmd/gitbay/main.go +2 −1
| @@ -129,7 +129,8 @@ func newRoot() *cobra.Command { | |||
| 129 | pass("invite", "issue a registration invite and mail its code: --email <address>", passOpts{server: []string{"admin", "invite"}}), | 129 | pass("invite", "issue a registration invite and mail its code: --email <address>", passOpts{server: []string{"admin", "invite"}}), |
| 130 | pass("stats", "instance statistics: counts and per-repository disk usage", passOpts{server: []string{"admin", "stats"}}), | 130 | pass("stats", "instance statistics: counts and per-repository disk usage", passOpts{server: []string{"admin", "stats"}}), |
| 131 | withSub(pass("runners", "the build queue and runner keys: last poll, scope, the build each holds", passOpts{server: []string{"admin", "runners"}}), | 131 | withSub(pass("runners", "the build queue and runner keys: last poll, scope, the build each holds", passOpts{server: []string{"admin", "runners"}}), |
| 132 | pass("forget", "drop a key's heartbeat row: <fingerprint>", passOpts{server: []string{"admin", "runners", "forget"}})), | 132 | pass("remove", "drop a key's heartbeat row: <fingerprint>", passOpts{server: []string{"admin", "runners", "remove"}}), |
| 133 | pass("forget", "alias of remove: <fingerprint>", passOpts{server: []string{"admin", "runners", "forget"}})), | ||
| 133 | group("repo", "any repository, for moderation (audited)", | 134 | group("repo", "any repository, for moderation (audited)", |
| 134 | pass("list", "every repository with size and last push: [--owner o] [--visibility v] [--limit n] [--cursor c]", passOpts{server: []string{"admin", "repo", "list"}}), | 135 | pass("list", "every repository with size and last push: [--owner o] [--visibility v] [--limit n] [--cursor c]", passOpts{server: []string{"admin", "repo", "list"}}), |
| 135 | pass("archive", "archive a repository: <owner/name>", passOpts{server: []string{"admin", "repo", "archive"}}), | 136 | pass("archive", "archive a repository: <owner/name>", passOpts{server: []string{"admin", "repo", "archive"}}), |
cmd/gitbay/ssh.go +41
| @@ -1,8 +1,11 @@ | |||
| 1 | package main | 1 | package main |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "text/tabwriter" | ||
| 5 | |||
| 4 | "encoding/json" | 6 | "encoding/json" |
| 5 | "fmt" | 7 | "fmt" |
| 8 | "golang.org/x/term" | ||
| 6 | "io" | 9 | "io" |
| 7 | "os" | 10 | "os" |
| 8 | "os/exec" | 11 | "os/exec" |
| @@ -120,7 +123,15 @@ func runSSH(t target, serverArgv []string, stdin io.Reader) int { | |||
| 120 | cmd.Stdin = stdin | 123 | cmd.Stdin = stdin |
| 121 | cmd.Stdout = os.Stdout | 124 | cmd.Stdout = os.Stdout |
| 122 | cmd.Stderr = os.Stderr | 125 | cmd.Stderr = os.Stderr |
| 126 | var tw *tabwriter.Writer | ||
| 127 | if alignColumns(serverArgv) { | ||
| 128 | tw = tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', 0) | ||
| 129 | cmd.Stdout = tw | ||
| 130 | } | ||
| 123 | err := cmd.Run() | 131 | err := cmd.Run() |
| 132 | if tw != nil { | ||
| 133 | tw.Flush() | ||
| 134 | } | ||
| 124 | if err == nil { | 135 | if err == nil { |
| 125 | return 0 | 136 | return 0 |
| 126 | } | 137 | } |
| @@ -193,3 +204,33 @@ func withRepo(t target, args []string) ([]string, error) { | |||
| 193 | } | 204 | } |
| 194 | return append([]string{t.repo}, args...), nil | 205 | return append([]string{t.repo}, args...), nil |
| 195 | } | 206 | } |
| 207 | |||
| 208 | // listVerbs are the server commands whose plain output is one row per | ||
| 209 | // item with tab-separated columns. | ||
| 210 | var listVerbs = map[string]bool{ | ||
| 211 | "list": true, "runners": true, "deliveries": true, "refs": true, | ||
| 212 | "revisions": true, "threads": true, "bookmarks": true, "jobs": true, | ||
| 213 | } | ||
| 214 | |||
| 215 | // alignColumns reports whether a command's rows should be padded into | ||
| 216 | // columns: a list command, printed for a person at a terminal. Piped | ||
| 217 | // output keeps the server's tabs so cut and awk see the same bytes stock | ||
| 218 | // ssh prints, and --json is never touched. | ||
| 219 | func alignColumns(serverArgv []string) bool { | ||
| 220 | verb := "" | ||
| 221 | for _, a := range serverArgv { | ||
| 222 | if strings.HasPrefix(a, "-") { | ||
| 223 | break | ||
| 224 | } | ||
| 225 | if a == "--json" { | ||
| 226 | return false | ||
| 227 | } | ||
| 228 | verb = a | ||
| 229 | } | ||
| 230 | for _, a := range serverArgv { | ||
| 231 | if a == "--json" { | ||
| 232 | return false | ||
| 233 | } | ||
| 234 | } | ||
| 235 | return listVerbs[verb] && term.IsTerminal(int(os.Stdout.Fd())) | ||
| 236 | } | ||
internal/control/admin.go +16 −10
| @@ -33,8 +33,14 @@ func init() { | |||
| 33 | Summary: "the build queue and runner accounts: last poll, scope, the build each holds (instance admins)", | 33 | Summary: "the build queue and runner accounts: last poll, scope, the build each holds (instance admins)", |
| 34 | Usage: "admin runners", | 34 | Usage: "admin runners", |
| 35 | ReadOnly: true, SSHOnly: true, Run: runAdminRunners}) | 35 | ReadOnly: true, SSHOnly: true, Run: runAdminRunners}) |
| 36 | register(Command{Path: []string{"admin", "runners", "forget"}, | 36 | register(Command{Path: []string{"admin", "runners", "remove"}, |
| 37 | Summary: "drop a key's runner heartbeat row, e.g. one that polled once by mistake (instance admins)", | 37 | Summary: "drop a key's runner heartbeat row, e.g. one that polled once by mistake (instance admins)", |
| 38 | Usage: "admin runners remove <fingerprint>", | ||
| 39 | SSHOnly: true, Run: runAdminRunnersForget}) | ||
| 40 | // forget is the name this shipped under in v1.18; remove is the verb | ||
| 41 | // every other noun uses. Both stay for one release. | ||
| 42 | register(Command{Path: []string{"admin", "runners", "forget"}, | ||
| 43 | Summary: "alias of admin runners remove", | ||
| 38 | Usage: "admin runners forget <fingerprint>", | 44 | Usage: "admin runners forget <fingerprint>", |
| 39 | SSHOnly: true, Run: runAdminRunnersForget}) | 45 | SSHOnly: true, Run: runAdminRunnersForget}) |
| 40 | register(Command{Path: []string{"admin", "repo", "list"}, | 46 | register(Command{Path: []string{"admin", "repo", "list"}, |
| @@ -62,7 +68,7 @@ func init() { | |||
| 62 | // requireInstanceAdmin gates the admin noun. -1 means proceed. | 68 | // requireInstanceAdmin gates the admin noun. -1 means proceed. |
| 63 | func requireInstanceAdmin(c *Ctx) int { | 69 | func requireInstanceAdmin(c *Ctx) int { |
| 64 | if !c.User.IsAdmin { | 70 | if !c.User.IsAdmin { |
| 65 | return c.fail(protocol.ExitDenied, "admin commands are for instance admins") | 71 | return c.fail(protocol.ExitDenied, "admin commands are for instance admins; ask one") |
| 66 | } | 72 | } |
| 67 | return -1 | 73 | return -1 |
| 68 | } | 74 | } |
| @@ -131,7 +137,7 @@ func runAdminUserShow(c *Ctx, args []string) int { | |||
| 131 | return code | 137 | return code |
| 132 | } | 138 | } |
| 133 | if len(args) != 1 { | 139 | if len(args) != 1 { |
| 134 | return c.fail(protocol.ExitUsage, "usage: admin user show <username>") | 140 | return c.usage() |
| 135 | } | 141 | } |
| 136 | name := args[0] | 142 | name := args[0] |
| 137 | u, err := c.Store.UserByUsername(name) | 143 | u, err := c.Store.UserByUsername(name) |
| @@ -291,7 +297,7 @@ func setAdmin(c *Ctx, args []string, admin bool) int { | |||
| 291 | verb = "promote" | 297 | verb = "promote" |
| 292 | } | 298 | } |
| 293 | if len(args) != 1 { | 299 | if len(args) != 1 { |
| 294 | return c.fail(protocol.ExitUsage, "usage: admin user %s <username>", verb) | 300 | return c.usage() |
| 295 | } | 301 | } |
| 296 | u, err := c.Store.UserByUsername(args[0]) | 302 | u, err := c.Store.UserByUsername(args[0]) |
| 297 | if errors.Is(err, store.ErrNotFound) { | 303 | if errors.Is(err, store.ErrNotFound) { |
| @@ -389,7 +395,7 @@ func adminArchive(c *Ctx, args []string, archived bool) int { | |||
| 389 | verb = "unarchive" | 395 | verb = "unarchive" |
| 390 | } | 396 | } |
| 391 | if len(args) != 1 { | 397 | if len(args) != 1 { |
| 392 | return c.fail(protocol.ExitUsage, "usage: admin repo %s <owner/name>", verb) | 398 | return c.usage() |
| 393 | } | 399 | } |
| 394 | repo, code := adminRepo(c, args[0]) | 400 | repo, code := adminRepo(c, args[0]) |
| 395 | if code >= 0 { | 401 | if code >= 0 { |
| @@ -404,7 +410,7 @@ func adminArchive(c *Ctx, args []string, archived bool) int { | |||
| 404 | 410 | ||
| 405 | func runAdminRepoVisibility(c *Ctx, args []string) int { | 411 | func runAdminRepoVisibility(c *Ctx, args []string) int { |
| 406 | if len(args) != 2 || (args[1] != "public" && args[1] != "private") { | 412 | if len(args) != 2 || (args[1] != "public" && args[1] != "private") { |
| 407 | return c.fail(protocol.ExitUsage, "usage: admin repo visibility <owner/name> public|private") | 413 | return c.usage() |
| 408 | } | 414 | } |
| 409 | repo, code := adminRepo(c, args[0]) | 415 | repo, code := adminRepo(c, args[0]) |
| 410 | if code >= 0 { | 416 | if code >= 0 { |
| @@ -426,11 +432,11 @@ func runAdminRepoDelete(c *Ctx, args []string) int { | |||
| 426 | } else if path == "" { | 432 | } else if path == "" { |
| 427 | path = a | 433 | path = a |
| 428 | } else { | 434 | } else { |
| 429 | return c.fail(protocol.ExitUsage, "usage: admin repo delete <owner/name> --yes") | 435 | return c.usage() |
| 430 | } | 436 | } |
| 431 | } | 437 | } |
| 432 | if path == "" { | 438 | if path == "" { |
| 433 | return c.fail(protocol.ExitUsage, "usage: admin repo delete <owner/name> --yes") | 439 | return c.usage() |
| 434 | } | 440 | } |
| 435 | repo, code := adminRepo(c, path) | 441 | repo, code := adminRepo(c, path) |
| 436 | if code >= 0 { | 442 | if code >= 0 { |
| @@ -451,7 +457,7 @@ func runAdminRunnersForget(c *Ctx, args []string) int { | |||
| 451 | return code | 457 | return code |
| 452 | } | 458 | } |
| 453 | if len(args) != 1 { | 459 | if len(args) != 1 { |
| 454 | return c.fail(protocol.ExitUsage, "usage: admin runners forget <fingerprint>") | 460 | return c.usage() |
| 455 | } | 461 | } |
| 456 | if err := c.Store.ForgetRunner(args[0]); err != nil { | 462 | if err := c.Store.ForgetRunner(args[0]); err != nil { |
| 457 | if errors.Is(err, store.ErrNotFound) { | 463 | if errors.Is(err, store.ErrNotFound) { |
| @@ -470,7 +476,7 @@ func runAdminRunners(c *Ctx, args []string) int { | |||
| 470 | return code | 476 | return code |
| 471 | } | 477 | } |
| 472 | if len(args) != 0 { | 478 | if len(args) != 0 { |
| 473 | return c.fail(protocol.ExitUsage, "usage: admin runners") | 479 | return c.usage() |
| 474 | } | 480 | } |
| 475 | runners, err := c.Store.ListRunners() | 481 | runners, err := c.Store.ListRunners() |
| 476 | if err != nil { | 482 | if err != nil { |
internal/control/adminhost.go +7 −8
| @@ -55,8 +55,7 @@ func runAdminUserCreate(c *Ctx, args []string) int { | |||
| 55 | if code := requireInstanceAdmin(c); code >= 0 { | 55 | if code := requireInstanceAdmin(c); code >= 0 { |
| 56 | return code | 56 | return code |
| 57 | } | 57 | } |
| 58 | const usage = "usage: admin user create <username> [--admin] [--email <address> [--verified]] [--key -] < key.pub" | 58 | f, err := parseFlags(args, flagSpec{Values: []string{"--email", "--key"}, Bools: []string{"--admin", "--verified"}, MaxPos: 1, Usage: c.Cmd.Usage}) |
| 59 | f, err := parseFlags(args, flagSpec{Values: []string{"--email", "--key"}, Bools: []string{"--admin", "--verified"}, MaxPos: 1, Usage: usage}) | ||
| 60 | if err != nil { | 59 | if err != nil { |
| 61 | return c.fail(protocol.ExitUsage, "%v", err) | 60 | return c.fail(protocol.ExitUsage, "%v", err) |
| 62 | } | 61 | } |
| @@ -66,10 +65,10 @@ func runAdminUserCreate(c *Ctx, args []string) int { | |||
| 66 | return c.fail(protocol.ExitUsage, "--key only supports - (the public key on stdin)") | 65 | return c.fail(protocol.ExitUsage, "--key only supports - (the public key on stdin)") |
| 67 | } | 66 | } |
| 68 | if username == "" || username[0] == '-' { | 67 | if username == "" || username[0] == '-' { |
| 69 | return c.fail(protocol.ExitUsage, usage) | 68 | return c.usage() |
| 70 | } | 69 | } |
| 71 | if username == "" || (verified && email == "") { | 70 | if username == "" || (verified && email == "") { |
| 72 | return c.fail(protocol.ExitUsage, usage) | 71 | return c.usage() |
| 73 | } | 72 | } |
| 74 | if err := policy.ValidateOwnerName(username); err != nil { | 73 | if err := policy.ValidateOwnerName(username); err != nil { |
| 75 | return c.failInput(err) | 74 | return c.failInput(err) |
| @@ -128,7 +127,7 @@ func adminUserArg(c *Ctx, args []string, usage string) (store.User, int) { | |||
| 128 | return store.User{}, code | 127 | return store.User{}, code |
| 129 | } | 128 | } |
| 130 | if len(args) != 1 { | 129 | if len(args) != 1 { |
| 131 | return store.User{}, c.fail(protocol.ExitUsage, "usage: %s", usage) | 130 | return store.User{}, c.usage() |
| 132 | } | 131 | } |
| 133 | u, err := c.Store.UserByUsername(args[0]) | 132 | u, err := c.Store.UserByUsername(args[0]) |
| 134 | if errors.Is(err, store.ErrNotFound) { | 133 | if errors.Is(err, store.ErrNotFound) { |
| @@ -201,7 +200,7 @@ func runAdminEmailVerify(c *Ctx, args []string) int { | |||
| 201 | return code | 200 | return code |
| 202 | } | 201 | } |
| 203 | if len(args) != 2 { | 202 | if len(args) != 2 { |
| 204 | return c.fail(protocol.ExitUsage, "usage: admin email verify <username> <address>") | 203 | return c.usage() |
| 205 | } | 204 | } |
| 206 | u, err := c.Store.UserByUsername(args[0]) | 205 | u, err := c.Store.UserByUsername(args[0]) |
| 207 | if errors.Is(err, store.ErrNotFound) { | 206 | if errors.Is(err, store.ErrNotFound) { |
| @@ -228,7 +227,7 @@ func runAdminInvite(c *Ctx, args []string) int { | |||
| 228 | email = args[1] | 227 | email = args[1] |
| 229 | } | 228 | } |
| 230 | if email == "" { | 229 | if email == "" { |
| 231 | return c.fail(protocol.ExitUsage, "usage: admin invite --email <address>") | 230 | return c.usage() |
| 232 | } | 231 | } |
| 233 | if used, err := c.Store.EmailInUse(email); err != nil { | 232 | if used, err := c.Store.EmailInUse(email); err != nil { |
| 234 | return c.fail(protocol.ExitFailure, "%v", err) | 233 | return c.fail(protocol.ExitFailure, "%v", err) |
| @@ -271,7 +270,7 @@ func runAdminStats(c *Ctx, args []string) int { | |||
| 271 | return code | 270 | return code |
| 272 | } | 271 | } |
| 273 | if len(args) != 0 { | 272 | if len(args) != 0 { |
| 274 | return c.fail(protocol.ExitUsage, "usage: admin stats") | 273 | return c.usage() |
| 275 | } | 274 | } |
| 276 | counts, err := c.Store.InstanceCounts() | 275 | counts, err := c.Store.InstanceCounts() |
| 277 | if err != nil { | 276 | if err != nil { |
internal/control/audit.go +2 −4
| @@ -18,14 +18,12 @@ func init() { | |||
| 18 | ReadOnly: true, SSHOnly: true, Run: runAudit}) | 18 | ReadOnly: true, SSHOnly: true, Run: runAudit}) |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | const auditUsage = "usage: audit [--actor <user>|-] [--action <prefix>] [--since <duration|date>] [--limit <n>]" | ||
| 22 | |||
| 23 | func runAudit(c *Ctx, args []string) int { | 21 | func runAudit(c *Ctx, args []string) int { |
| 24 | if !c.User.IsAdmin { | 22 | if !c.User.IsAdmin { |
| 25 | return c.fail(protocol.ExitDenied, "the audit log is for instance admins") | 23 | return c.fail(protocol.ExitDenied, "the audit log is for instance admins; ask one") |
| 26 | } | 24 | } |
| 27 | f := store.AuditFilter{Limit: 100} | 25 | f := store.AuditFilter{Limit: 100} |
| 28 | fl, err := parseFlags(args, flagSpec{Values: []string{"--limit", "--actor", "--action", "--since"}, MaxPos: 0, Usage: auditUsage}) | 26 | fl, err := parseFlags(args, flagSpec{Values: []string{"--limit", "--actor", "--action", "--since"}, MaxPos: 0, Usage: c.Cmd.Usage}) |
| 29 | if err != nil { | 27 | if err != nil { |
| 30 | return c.fail(protocol.ExitUsage, "%v", err) | 28 | return c.fail(protocol.ExitUsage, "%v", err) |
| 31 | } | 29 | } |
internal/control/build.go +12 −12
| @@ -104,7 +104,7 @@ func buildRef(c *Ctx, args []string) (store.Repo, store.Build, int) { | |||
| 104 | 104 | ||
| 105 | func runBuildList(c *Ctx, args []string) int { | 105 | func runBuildList(c *Ctx, args []string) int { |
| 106 | if len(args) != 1 { | 106 | if len(args) != 1 { |
| 107 | return c.fail(protocol.ExitUsage, "usage: build list <owner/name>") | 107 | return c.usage() |
| 108 | } | 108 | } |
| 109 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 109 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 110 | if code >= 0 { | 110 | if code >= 0 { |
| @@ -182,7 +182,7 @@ func repoJobs(c *Ctx, repo store.Repo) ([]ci.Job, string, int) { | |||
| 182 | // that can read the repository's git could offer the choice. | 182 | // that can read the repository's git could offer the choice. |
| 183 | func runBuildJobs(c *Ctx, args []string) int { | 183 | func runBuildJobs(c *Ctx, args []string) int { |
| 184 | if len(args) != 1 { | 184 | if len(args) != 1 { |
| 185 | return c.fail(protocol.ExitUsage, "usage: build jobs <owner/name>") | 185 | return c.usage() |
| 186 | } | 186 | } |
| 187 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 187 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 188 | if code >= 0 { | 188 | if code >= 0 { |
| @@ -212,7 +212,7 @@ func runBuildJobs(c *Ctx, args []string) int { | |||
| 212 | 212 | ||
| 213 | func runBuildTrigger(c *Ctx, args []string) int { | 213 | func runBuildTrigger(c *Ctx, args []string) int { |
| 214 | if len(args) != 2 { | 214 | if len(args) != 2 { |
| 215 | return c.fail(protocol.ExitUsage, "usage: build trigger <owner/name> <job>") | 215 | return c.usage() |
| 216 | } | 216 | } |
| 217 | repo, code := resolveRepo(c, args[0], policy.CanWrite) | 217 | repo, code := resolveRepo(c, args[0], policy.CanWrite) |
| 218 | if code >= 0 { | 218 | if code >= 0 { |
| @@ -246,7 +246,7 @@ var secretName = regexp.MustCompile(`^[A-Z_][A-Z0-9_]{0,63}$`) | |||
| 246 | 246 | ||
| 247 | func runSecretSet(c *Ctx, args []string) int { | 247 | func runSecretSet(c *Ctx, args []string) int { |
| 248 | if len(args) != 2 { | 248 | if len(args) != 2 { |
| 249 | return c.fail(protocol.ExitUsage, "usage: repo secret set <owner/name> <NAME> (value on stdin)") | 249 | return c.usage() |
| 250 | } | 250 | } |
| 251 | if !secretName.MatchString(args[1]) { | 251 | if !secretName.MatchString(args[1]) { |
| 252 | return c.fail(protocol.ExitUsage, "secret names are env-var shaped: uppercase letters, digits, _") | 252 | return c.fail(protocol.ExitUsage, "secret names are env-var shaped: uppercase letters, digits, _") |
| @@ -273,7 +273,7 @@ func runSecretSet(c *Ctx, args []string) int { | |||
| 273 | 273 | ||
| 274 | func runSecretRemove(c *Ctx, args []string) int { | 274 | func runSecretRemove(c *Ctx, args []string) int { |
| 275 | if len(args) != 2 { | 275 | if len(args) != 2 { |
| 276 | return c.fail(protocol.ExitUsage, "usage: repo secret remove <owner/name> <NAME>") | 276 | return c.usage() |
| 277 | } | 277 | } |
| 278 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 278 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 279 | if code >= 0 { | 279 | if code >= 0 { |
| @@ -292,7 +292,7 @@ func runSecretRemove(c *Ctx, args []string) int { | |||
| 292 | 292 | ||
| 293 | func runSecretList(c *Ctx, args []string) int { | 293 | func runSecretList(c *Ctx, args []string) int { |
| 294 | if len(args) != 1 { | 294 | if len(args) != 1 { |
| 295 | return c.fail(protocol.ExitUsage, "usage: repo secret list <owner/name>") | 295 | return c.usage() |
| 296 | } | 296 | } |
| 297 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 297 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 298 | if code >= 0 { | 298 | if code >= 0 { |
| @@ -376,7 +376,7 @@ func runRunnerNext(c *Ctx, args []string) int { | |||
| 376 | return c.fail(protocol.ExitFailure, "%v", err) | 376 | return c.fail(protocol.ExitFailure, "%v", err) |
| 377 | } | 377 | } |
| 378 | if !ok { | 378 | if !ok { |
| 379 | return c.fail(protocol.ExitDenied, "this key is not attached to %s", repo.Path()) | 379 | return c.fail(protocol.ExitDenied, "this key is not attached to %s; a repository admin attaches it with repo runner add", repo.Path()) |
| 380 | } | 380 | } |
| 381 | repoIDs = append(repoIDs, repo.ID) | 381 | repoIDs = append(repoIDs, repo.ID) |
| 382 | } | 382 | } |
| @@ -464,7 +464,7 @@ func runRunnerLog(c *Ctx, args []string) int { | |||
| 464 | return code | 464 | return code |
| 465 | } | 465 | } |
| 466 | if len(args) != 1 { | 466 | if len(args) != 1 { |
| 467 | return c.fail(protocol.ExitUsage, "usage: runner log <build-id> (chunk on stdin)") | 467 | return c.usage() |
| 468 | } | 468 | } |
| 469 | id, err := strconv.ParseInt(args[0], 10, 64) | 469 | id, err := strconv.ParseInt(args[0], 10, 64) |
| 470 | if err != nil { | 470 | if err != nil { |
| @@ -475,7 +475,7 @@ func runRunnerLog(c *Ctx, args []string) int { | |||
| 475 | } else if ok, err := runnerMayBuild(c, key, b.RepoID); err != nil { | 475 | } else if ok, err := runnerMayBuild(c, key, b.RepoID); err != nil { |
| 476 | return c.fail(protocol.ExitFailure, "%v", err) | 476 | return c.fail(protocol.ExitFailure, "%v", err) |
| 477 | } else if !ok { | 477 | } else if !ok { |
| 478 | return c.fail(protocol.ExitDenied, "this key is not attached to the build's repository") | 478 | return c.fail(protocol.ExitDenied, "this key is not attached to the build's repository; a repository admin attaches it with repo runner add") |
| 479 | } | 479 | } |
| 480 | // Stream stdin into the log in chunks so long builds appear live. An | 480 | // Stream stdin into the log in chunks so long builds appear live. An |
| 481 | // append that fails drops its chunk and the loop keeps draining: ending | 481 | // append that fails drops its chunk and the loop keeps draining: ending |
| @@ -542,7 +542,7 @@ func runRunnerDone(c *Ctx, args []string) int { | |||
| 542 | return code | 542 | return code |
| 543 | } | 543 | } |
| 544 | if len(args) != 2 || (args[1] != "success" && args[1] != "failure") { | 544 | if len(args) != 2 || (args[1] != "success" && args[1] != "failure") { |
| 545 | return c.fail(protocol.ExitUsage, "usage: runner done <build-id> success|failure") | 545 | return c.usage() |
| 546 | } | 546 | } |
| 547 | id, err := strconv.ParseInt(args[0], 10, 64) | 547 | id, err := strconv.ParseInt(args[0], 10, 64) |
| 548 | if err != nil { | 548 | if err != nil { |
| @@ -555,7 +555,7 @@ func runRunnerDone(c *Ctx, args []string) int { | |||
| 555 | if ok, err := runnerMayBuild(c, key, b.RepoID); err != nil { | 555 | if ok, err := runnerMayBuild(c, key, b.RepoID); err != nil { |
| 556 | return c.fail(protocol.ExitFailure, "%v", err) | 556 | return c.fail(protocol.ExitFailure, "%v", err) |
| 557 | } else if !ok { | 557 | } else if !ok { |
| 558 | return c.fail(protocol.ExitDenied, "this key is not attached to the build's repository") | 558 | return c.fail(protocol.ExitDenied, "this key is not attached to the build's repository; a repository admin attaches it with repo runner add") |
| 559 | } | 559 | } |
| 560 | // Cancelled underneath the runner: its report is late, not wrong. | 560 | // Cancelled underneath the runner: its report is late, not wrong. |
| 561 | // The row, the status and the log were settled by the cancel. | 561 | // The row, the status and the log were settled by the cancel. |
| @@ -829,7 +829,7 @@ func runBuildCancel(c *Ctx, args []string) int { | |||
| 829 | return c.fail(protocol.ExitFailure, "%v", err) | 829 | return c.fail(protocol.ExitFailure, "%v", err) |
| 830 | } | 830 | } |
| 831 | if !policy.CanWrite(c.User, repo, grant) { | 831 | if !policy.CanWrite(c.User, repo, grant) { |
| 832 | return c.fail(protocol.ExitDenied, "cancelling a build needs write access to %s", repo.Path()) | 832 | return c.fail(protocol.ExitDenied, "cancelling a build needs write access to %s; ask its owner", repo.Path()) |
| 833 | } | 833 | } |
| 834 | if b.Status != "pending" && b.Status != "running" { | 834 | if b.Status != "pending" && b.Status != "running" { |
| 835 | return c.fail(protocol.ExitUsage, "build %d is %s; only a queued or running build can be cancelled", b.Number, b.Status) | 835 | return c.fail(protocol.ExitUsage, "build %d is %s; only a queued or running build can be cancelled", b.Number, b.Status) |
internal/control/commitfile.go +3 −4
| @@ -35,15 +35,14 @@ const maxCommitFileBytes = 1 << 20 | |||
| 35 | // A repository that requires verified signatures therefore refuses the | 35 | // A repository that requires verified signatures therefore refuses the |
| 36 | // command rather than writing a commit its own policy would reject. | 36 | // command rather than writing a commit its own policy would reject. |
| 37 | func runCommitFile(c *Ctx, args []string) int { | 37 | func runCommitFile(c *Ctx, args []string) int { |
| 38 | const usage = "repo commit-file <owner/name> <path> --ref <branch> [--message <m>] [--file -]" | 38 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref", "--message", "--file"}, MaxPos: -1, Usage: c.Cmd.Usage}) |
| 39 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref", "--message", "--file"}, MaxPos: -1, Usage: usage}) | ||
| 40 | if err != nil { | 39 | if err != nil { |
| 41 | return c.fail(protocol.ExitUsage, "%v", err) | 40 | return c.fail(protocol.ExitUsage, "%v", err) |
| 42 | } | 41 | } |
| 43 | rest := f.Pos | 42 | rest := f.Pos |
| 44 | ref, message, file := f.Value("--ref"), f.Value("--message"), f.Value("--file") | 43 | ref, message, file := f.Value("--ref"), f.Value("--message"), f.Value("--file") |
| 45 | if len(rest) != 2 || ref == "" { | 44 | if len(rest) != 2 || ref == "" { |
| 46 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 45 | return c.usage() |
| 47 | } | 46 | } |
| 48 | repo, code := resolveRepo(c, rest[0], policy.CanWrite) | 47 | repo, code := resolveRepo(c, rest[0], policy.CanWrite) |
| 49 | if code >= 0 { | 48 | if code >= 0 { |
| @@ -57,7 +56,7 @@ func runCommitFile(c *Ctx, args []string) int { | |||
| 57 | return c.fail(protocol.ExitUsage, "path must stay inside the repository") | 56 | return c.fail(protocol.ExitUsage, "path must stay inside the repository") |
| 58 | } | 57 | } |
| 59 | if repo.Settings.RequireMR && slices.Contains(repo.Settings.ProtectedBranches, ref) { | 58 | if repo.Settings.RequireMR && slices.Contains(repo.Settings.ProtectedBranches, ref) { |
| 60 | return c.fail(protocol.ExitDenied, "branch %s accepts changes through merge requests only", ref) | 59 | return c.fail(protocol.ExitDenied, "branch %s accepts changes through merge requests only; push another branch and open one", ref) |
| 61 | } | 60 | } |
| 62 | // The server authors this commit, so it cannot sign it. | 61 | // The server authors this commit, so it cannot sign it. |
| 63 | if repo.Settings.RequireSignedCommits { | 62 | if repo.Settings.RequireSignedCommits { |
internal/control/control.go +22 −5
| @@ -36,6 +36,15 @@ type Ctx struct { | |||
| 36 | // Source identifies the credential behind this session for the audit | 36 | // Source identifies the credential behind this session for the audit |
| 37 | // log: an SSH key fingerprint, or "api" for token requests. | 37 | // log: an SSH key fingerprint, or "api" for token requests. |
| 38 | Source string | 38 | Source string |
| 39 | // Cmd is the command being run, set by Dispatch, so a usage error can | ||
| 40 | // print the registered usage rather than a copy of it. | ||
| 41 | Cmd Command | ||
| 42 | } | ||
| 43 | |||
| 44 | // usage reports a bad invocation with the command's registered usage, | ||
| 45 | // the one source of it. | ||
| 46 | func (c *Ctx) usage() int { | ||
| 47 | return c.fail(protocol.ExitUsage, "usage: %s", c.Cmd.Usage) | ||
| 39 | } | 48 | } |
| 40 | 49 | ||
| 41 | type Command struct { | 50 | type Command struct { |
| @@ -82,6 +91,7 @@ func Dispatch(c *Ctx, argv []string) int { | |||
| 82 | return c.fail(protocol.ExitUsage, "no command given; try: ssh <host> help") | 91 | return c.fail(protocol.ExitUsage, "no command given; try: ssh <host> help") |
| 83 | } | 92 | } |
| 84 | cmd, rest, ok := Lookup(argv) | 93 | cmd, rest, ok := Lookup(argv) |
| 94 | c.Cmd = cmd | ||
| 85 | if !ok { | 95 | if !ok { |
| 86 | return c.fail(protocol.ExitUsage, "unknown command %q", argv[0]) | 96 | return c.fail(protocol.ExitUsage, "unknown command %q", argv[0]) |
| 87 | } | 97 | } |
| @@ -99,23 +109,23 @@ func Dispatch(c *Ctx, argv []string) int { | |||
| 99 | // A runner-scoped key reaches the runner protocol and nothing else, so | 109 | // A runner-scoped key reaches the runner protocol and nothing else, so |
| 100 | // the key a CI host holds cannot administer the instance. | 110 | // the key a CI host holds cannot administer the instance. |
| 101 | if c.Scope != "full" && !(c.Scope == "runner" && cmd.Path[0] == "runner") { | 111 | if c.Scope != "full" && !(c.Scope == "runner" && cmd.Path[0] == "runner") { |
| 102 | return c.fail(protocol.ExitDenied, "this key's scope (%s) does not allow control commands", c.Scope) | 112 | return c.fail(protocol.ExitDenied, "this key's scope (%s) does not allow control commands; use a key added with --scope full", c.Scope) |
| 103 | } | 113 | } |
| 104 | if c.ViaAPI && cmd.SSHOnly { | 114 | if c.ViaAPI && cmd.SSHOnly { |
| 105 | return c.fail(protocol.ExitDenied, "%s is only available over SSH", joinPath(cmd.Path)) | 115 | return c.fail(protocol.ExitDenied, "%s is only available over SSH", joinPath(cmd.Path)) |
| 106 | } | 116 | } |
| 107 | if c.ReadOnly && !cmd.ReadOnly { | 117 | if c.ReadOnly && !cmd.ReadOnly { |
| 108 | return c.fail(protocol.ExitDenied, "this token is read-only; %s modifies state", joinPath(cmd.Path)) | 118 | return c.fail(protocol.ExitDenied, "this token is read-only; %s modifies state — mint one with --scope full", joinPath(cmd.Path)) |
| 109 | } | 119 | } |
| 110 | // The SSH listener refuses a disabled account before it gets here; the | 120 | // The SSH listener refuses a disabled account before it gets here; the |
| 111 | // API and the web reach Dispatch directly, so the check lives here too. | 121 | // API and the web reach Dispatch directly, so the check lives here too. |
| 112 | if c.User.Disabled { | 122 | if c.User.Disabled { |
| 113 | return c.fail(protocol.ExitDenied, "this account is disabled") | 123 | return c.fail(protocol.ExitDenied, "this account is disabled; ask an instance admin to enable it") |
| 114 | } | 124 | } |
| 115 | // The admin noun is gated here as well as in each handler, so a new | 125 | // The admin noun is gated here as well as in each handler, so a new |
| 116 | // admin command that forgets requireInstanceAdmin is still refused. | 126 | // admin command that forgets requireInstanceAdmin is still refused. |
| 117 | if cmd.Path[0] == "admin" && !c.User.IsAdmin { | 127 | if cmd.Path[0] == "admin" && !c.User.IsAdmin { |
| 118 | return c.fail(protocol.ExitDenied, "admin commands are for instance admins") | 128 | return c.fail(protocol.ExitDenied, "admin commands are for instance admins; ask one") |
| 119 | } | 129 | } |
| 120 | if c.User.Pending && !pendingAllowed(cmd.Path) { | 130 | if c.User.Pending && !pendingAllowed(cmd.Path) { |
| 121 | return c.fail(protocol.ExitDenied, | 131 | return c.fail(protocol.ExitDenied, |
| @@ -208,9 +218,16 @@ func (emptyReader) Read([]byte) (int, error) { return 0, io.EOF } | |||
| 208 | // otherwise via the plain formatter. | 218 | // otherwise via the plain formatter. |
| 209 | func (c *Ctx) emit(data any, plain func(w io.Writer)) int { | 219 | func (c *Ctx) emit(data any, plain func(w io.Writer)) int { |
| 210 | // A nil slice would serialize as null; consumers should see []. | 220 | // A nil slice would serialize as null; consumers should see []. |
| 211 | if v := reflect.ValueOf(data); v.Kind() == reflect.Slice && v.IsNil() { | 221 | v := reflect.ValueOf(data) |
| 222 | if v.Kind() == reflect.Slice && v.IsNil() { | ||
| 212 | data = reflect.MakeSlice(v.Type(), 0, 0).Interface() | 223 | data = reflect.MakeSlice(v.Type(), 0, 0).Interface() |
| 213 | } | 224 | } |
| 225 | // An empty list prints nothing a script would read; the person at | ||
| 226 | // the terminal hears about it on stderr. | ||
| 227 | if !c.JSON && v.Kind() == reflect.Slice && v.Len() == 0 { | ||
| 228 | fmt.Fprintln(c.Stderr, "nothing to list") | ||
| 229 | return protocol.ExitOK | ||
| 230 | } | ||
| 214 | if c.JSON { | 231 | if c.JSON { |
| 215 | enc := json.NewEncoder(c.Stdout) | 232 | enc := json.NewEncoder(c.Stdout) |
| 216 | enc.SetEscapeHTML(false) | 233 | enc.SetEscapeHTML(false) |
internal/control/dashboard.go +2 −2
| @@ -84,7 +84,7 @@ type DashboardOut struct { | |||
| 84 | 84 | ||
| 85 | func runDashboard(c *Ctx, args []string) int { | 85 | func runDashboard(c *Ctx, args []string) int { |
| 86 | if len(args) != 0 { | 86 | if len(args) != 0 { |
| 87 | return c.fail(protocol.ExitUsage, "usage: dashboard") | 87 | return c.usage() |
| 88 | } | 88 | } |
| 89 | d := DashboardOut{ | 89 | d := DashboardOut{ |
| 90 | Reviews: []DashboardItem{}, Assigned: []DashboardItem{}, MRs: []DashboardItem{}, | 90 | Reviews: []DashboardItem{}, Assigned: []DashboardItem{}, MRs: []DashboardItem{}, |
| @@ -260,7 +260,7 @@ func runFeed(c *Ctx, args []string) int { | |||
| 260 | return code | 260 | return code |
| 261 | } | 261 | } |
| 262 | if len(rest) != 0 { | 262 | if len(rest) != 0 { |
| 263 | return c.fail(protocol.ExitUsage, "usage: feed [--limit <n>] [--cursor <c>]") | 263 | return c.usage() |
| 264 | } | 264 | } |
| 265 | if p.limit == 0 { | 265 | if p.limit == 0 { |
| 266 | p.limit = feedDefaultLimit | 266 | p.limit = feedDefaultLimit |
internal/control/deploykey.go +4 −4
| @@ -34,13 +34,13 @@ func runDeployKeyAdd(c *Ctx, args []string) int { | |||
| 34 | mode = "rw" | 34 | mode = "rw" |
| 35 | default: | 35 | default: |
| 36 | if path != "" { | 36 | if path != "" { |
| 37 | return c.fail(protocol.ExitUsage, "usage: repo deploy-key add <owner/name> [--rw] < key.pub") | 37 | return c.usage() |
| 38 | } | 38 | } |
| 39 | path = a | 39 | path = a |
| 40 | } | 40 | } |
| 41 | } | 41 | } |
| 42 | if path == "" { | 42 | if path == "" { |
| 43 | return c.fail(protocol.ExitUsage, "usage: repo deploy-key add <owner/name> [--rw] < key.pub") | 43 | return c.usage() |
| 44 | } | 44 | } |
| 45 | repo, code := resolveRepo(c, path, policy.CanAdmin) | 45 | repo, code := resolveRepo(c, path, policy.CanAdmin) |
| 46 | if code >= 0 { | 46 | if code >= 0 { |
| @@ -73,7 +73,7 @@ func runDeployKeyAdd(c *Ctx, args []string) int { | |||
| 73 | 73 | ||
| 74 | func runDeployKeyList(c *Ctx, args []string) int { | 74 | func runDeployKeyList(c *Ctx, args []string) int { |
| 75 | if len(args) != 1 { | 75 | if len(args) != 1 { |
| 76 | return c.fail(protocol.ExitUsage, "usage: repo deploy-key list <owner/name>") | 76 | return c.usage() |
| 77 | } | 77 | } |
| 78 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 78 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 79 | if code >= 0 { | 79 | if code >= 0 { |
| @@ -106,7 +106,7 @@ func runDeployKeyList(c *Ctx, args []string) int { | |||
| 106 | 106 | ||
| 107 | func runDeployKeyRemove(c *Ctx, args []string) int { | 107 | func runDeployKeyRemove(c *Ctx, args []string) int { |
| 108 | if len(args) != 2 { | 108 | if len(args) != 2 { |
| 109 | return c.fail(protocol.ExitUsage, "usage: repo deploy-key remove <owner/name> <fingerprint>") | 109 | return c.usage() |
| 110 | } | 110 | } |
| 111 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 111 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 112 | if code >= 0 { | 112 | if code >= 0 { |
internal/control/deps.go +3 −3
| @@ -46,7 +46,7 @@ type DepBehind struct { | |||
| 46 | 46 | ||
| 47 | func runDepsEnable(c *Ctx, args []string) int { | 47 | func runDepsEnable(c *Ctx, args []string) int { |
| 48 | if len(args) != 1 { | 48 | if len(args) != 1 { |
| 49 | return c.fail(protocol.ExitUsage, "usage: repo deps enable <owner/name>") | 49 | return c.usage() |
| 50 | } | 50 | } |
| 51 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 51 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 52 | if code >= 0 { | 52 | if code >= 0 { |
| @@ -62,7 +62,7 @@ func runDepsEnable(c *Ctx, args []string) int { | |||
| 62 | 62 | ||
| 63 | func runDepsDisable(c *Ctx, args []string) int { | 63 | func runDepsDisable(c *Ctx, args []string) int { |
| 64 | if len(args) != 1 { | 64 | if len(args) != 1 { |
| 65 | return c.fail(protocol.ExitUsage, "usage: repo deps disable <owner/name>") | 65 | return c.usage() |
| 66 | } | 66 | } |
| 67 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 67 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 68 | if code >= 0 { | 68 | if code >= 0 { |
| @@ -78,7 +78,7 @@ func runDepsDisable(c *Ctx, args []string) int { | |||
| 78 | 78 | ||
| 79 | func runDepsStatus(c *Ctx, args []string) int { | 79 | func runDepsStatus(c *Ctx, args []string) int { |
| 80 | if len(args) != 1 { | 80 | if len(args) != 1 { |
| 81 | return c.fail(protocol.ExitUsage, "usage: repo deps status <owner/name>") | 81 | return c.usage() |
| 82 | } | 82 | } |
| 83 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 83 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 84 | if code >= 0 { | 84 | if code >= 0 { |
internal/control/diffcomment.go +2 −2
| @@ -143,7 +143,7 @@ func runMRThreads(c *Ctx, args []string) int { | |||
| 143 | return code | 143 | return code |
| 144 | } | 144 | } |
| 145 | if len(args) != 2 { | 145 | if len(args) != 2 { |
| 146 | return c.fail(protocol.ExitUsage, "usage: mr threads <owner/name> <n>") | 146 | return c.usage() |
| 147 | } | 147 | } |
| 148 | comments, err := c.Store.ListDiffComments(mr.ID, c.User.ID) | 148 | comments, err := c.Store.ListDiffComments(mr.ID, c.User.ID) |
| 149 | if err != nil { | 149 | if err != nil { |
| @@ -202,7 +202,7 @@ func runMRThreads(c *Ctx, args []string) int { | |||
| 202 | 202 | ||
| 203 | func setThreadResolved(c *Ctx, args []string, resolved bool) int { | 203 | func setThreadResolved(c *Ctx, args []string, resolved bool) int { |
| 204 | if len(args) != 3 { | 204 | if len(args) != 3 { |
| 205 | return c.fail(protocol.ExitUsage, "usage: mr resolve|unresolve <owner/name> <n> <thread-id>") | 205 | return c.usage() |
| 206 | } | 206 | } |
| 207 | repo, mr, code := mrRef(c, args[:2], policy.CanRead) | 207 | repo, mr, code := mrRef(c, args[:2], policy.CanRead) |
| 208 | if code >= 0 { | 208 | if code >= 0 { |
internal/control/explore.go +3 −4
| @@ -37,7 +37,7 @@ func runExplore(c *Ctx, args []string) int { | |||
| 37 | return code | 37 | return code |
| 38 | } | 38 | } |
| 39 | if len(rest) != 0 { | 39 | if len(rest) != 0 { |
| 40 | return c.fail(protocol.ExitUsage, "usage: explore [--limit <n>] [--cursor <c>]") | 40 | return c.usage() |
| 41 | } | 41 | } |
| 42 | repos, err := c.Store.ListPublicRepos() | 42 | repos, err := c.Store.ListPublicRepos() |
| 43 | if err != nil { | 43 | if err != nil { |
| @@ -79,14 +79,13 @@ func runExplore(c *Ctx, args []string) int { | |||
| 79 | // release asset get writes an asset. The web's /archive route is the | 79 | // release asset get writes an asset. The web's /archive route is the |
| 80 | // same bytes with a Content-Disposition on them. | 80 | // same bytes with a Content-Disposition on them. |
| 81 | func runRepoDownload(c *Ctx, args []string) int { | 81 | func runRepoDownload(c *Ctx, args []string) int { |
| 82 | const usage = "repo download <owner/name> [--ref <r>] > repo.tar.gz" | 82 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref"}, MaxPos: -1, Usage: c.Cmd.Usage}) |
| 83 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref"}, MaxPos: -1, Usage: usage}) | ||
| 84 | if err != nil { | 83 | if err != nil { |
| 85 | return c.fail(protocol.ExitUsage, "%v", err) | 84 | return c.fail(protocol.ExitUsage, "%v", err) |
| 86 | } | 85 | } |
| 87 | rest, ref := f.Pos, f.Value("--ref") | 86 | rest, ref := f.Pos, f.Value("--ref") |
| 88 | if len(rest) != 1 { | 87 | if len(rest) != 1 { |
| 89 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 88 | return c.usage() |
| 90 | } | 89 | } |
| 91 | repo, code := resolveRepo(c, rest[0], policy.CanRead) | 90 | repo, code := resolveRepo(c, rest[0], policy.CanRead) |
| 92 | if code >= 0 { | 91 | if code >= 0 { |
internal/control/ghimport.go +1 −1
| @@ -149,7 +149,7 @@ func runImportIssues(c *Ctx, args []string) int { | |||
| 149 | } | 149 | } |
| 150 | path, from, apiBase, tokenStdin := f.pos(0), f.Value("--from"), f.Value("--api-base"), f.Has("--token-stdin") | 150 | path, from, apiBase, tokenStdin := f.pos(0), f.Value("--from"), f.Value("--api-base"), f.Has("--token-stdin") |
| 151 | if path == "" || from == "" { | 151 | if path == "" || from == "" { |
| 152 | return c.fail(protocol.ExitUsage, "usage: repo import-issues <owner/name> --from <owner/repo> [--token-stdin] [--api-base <url>]") | 152 | return c.usage() |
| 153 | } | 153 | } |
| 154 | if apiBase == "" { | 154 | if apiBase == "" { |
| 155 | apiBase = "https://api.github.com" | 155 | apiBase = "https://api.github.com" |
internal/control/identity.go +4 −4
| @@ -51,7 +51,7 @@ func init() { | |||
| 51 | 51 | ||
| 52 | func runWhoami(c *Ctx, args []string) int { | 52 | func runWhoami(c *Ctx, args []string) int { |
| 53 | if len(args) != 0 { | 53 | if len(args) != 0 { |
| 54 | return c.fail(protocol.ExitUsage, "usage: whoami [--json]") | 54 | return c.usage() |
| 55 | } | 55 | } |
| 56 | type out struct { | 56 | type out struct { |
| 57 | Username string `json:"username"` | 57 | Username string `json:"username"` |
| @@ -66,7 +66,7 @@ func runWhoami(c *Ctx, args []string) int { | |||
| 66 | 66 | ||
| 67 | func runKeysList(c *Ctx, args []string) int { | 67 | func runKeysList(c *Ctx, args []string) int { |
| 68 | if len(args) != 0 { | 68 | if len(args) != 0 { |
| 69 | return c.fail(protocol.ExitUsage, "usage: keys list [--json]") | 69 | return c.usage() |
| 70 | } | 70 | } |
| 71 | keys, err := c.Store.ListSSHKeys(c.User.ID) | 71 | keys, err := c.Store.ListSSHKeys(c.User.ID) |
| 72 | if err != nil { | 72 | if err != nil { |
| @@ -161,7 +161,7 @@ func runKeysAdd(c *Ctx, args []string) int { | |||
| 161 | 161 | ||
| 162 | func runKeysLabel(c *Ctx, args []string) int { | 162 | func runKeysLabel(c *Ctx, args []string) int { |
| 163 | if len(args) < 1 || len(args) > 2 { | 163 | if len(args) < 1 || len(args) > 2 { |
| 164 | return c.fail(protocol.ExitUsage, "usage: keys label <fingerprint> [<text>]") | 164 | return c.usage() |
| 165 | } | 165 | } |
| 166 | label := "" | 166 | label := "" |
| 167 | if len(args) == 2 { | 167 | if len(args) == 2 { |
| @@ -189,7 +189,7 @@ func runKeysLabel(c *Ctx, args []string) int { | |||
| 189 | 189 | ||
| 190 | func runKeysRemove(c *Ctx, args []string) int { | 190 | func runKeysRemove(c *Ctx, args []string) int { |
| 191 | if len(args) != 1 { | 191 | if len(args) != 1 { |
| 192 | return c.fail(protocol.ExitUsage, "usage: keys remove <fingerprint>") | 192 | return c.usage() |
| 193 | } | 193 | } |
| 194 | if err := c.Store.RemoveSSHKey(c.User.ID, args[0]); err != nil { | 194 | if err := c.Store.RemoveSSHKey(c.User.ID, args[0]); err != nil { |
| 195 | if errors.Is(err, store.ErrNotFound) { | 195 | if errors.Is(err, store.ErrNotFound) { |
internal/control/import.go +2 −2
| @@ -40,11 +40,11 @@ func runRepoImport(c *Ctx, args []string) int { | |||
| 40 | } | 40 | } |
| 41 | path, from, private, tokenStdin := f.pos(0), f.Value("--from"), f.Has("--private"), f.Has("--token-stdin") | 41 | path, from, private, tokenStdin := f.pos(0), f.Value("--from"), f.Has("--private"), f.Has("--token-stdin") |
| 42 | if path == "" || from == "" { | 42 | if path == "" || from == "" { |
| 43 | return c.fail(protocol.ExitUsage, "usage: repo import <owner/name> --from <url> [--private] [--token-stdin]") | 43 | return c.usage() |
| 44 | } | 44 | } |
| 45 | owner, name, ok := strings.Cut(path, "/") | 45 | owner, name, ok := strings.Cut(path, "/") |
| 46 | if !ok { | 46 | if !ok { |
| 47 | return c.fail(protocol.ExitUsage, "usage: repo import <owner/name> --from <url>") | 47 | return c.usage() |
| 48 | } | 48 | } |
| 49 | if err := policy.ValidateName(name); err != nil { | 49 | if err := policy.ValidateName(name); err != nil { |
| 50 | return c.failInput(err) | 50 | return c.failInput(err) |
internal/control/issue.go +8 −9
| @@ -123,7 +123,7 @@ func runIssueCreate(c *Ctx, args []string) int { | |||
| 123 | } | 123 | } |
| 124 | path, title, body, file, format := f.pos(0), f.Value("--title"), f.Value("--body"), f.Value("--file"), f.Value("--format") | 124 | path, title, body, file, format := f.pos(0), f.Value("--title"), f.Value("--body"), f.Value("--file"), f.Value("--format") |
| 125 | if path == "" || title == "" { | 125 | if path == "" || title == "" { |
| 126 | return c.fail(protocol.ExitUsage, "usage: issue create <owner/name> --title <t> [--body <b> | --file -] [--format md|org]") | 126 | return c.usage() |
| 127 | } | 127 | } |
| 128 | fmtName, err := markupFormat(format) | 128 | fmtName, err := markupFormat(format) |
| 129 | if err != nil { | 129 | if err != nil { |
| @@ -168,9 +168,8 @@ func runIssueList(c *Ctx, args []string) int { | |||
| 168 | if code >= 0 { | 168 | if code >= 0 { |
| 169 | return code | 169 | return code |
| 170 | } | 170 | } |
| 171 | const usage = "usage: issue list <owner/name> [--state open|closed|all] [--label <l>] [--assignee <user>] [--author <user>] [--milestone <title>|none] [--search <text>] [--limit <n>] [--cursor <c>]" | ||
| 172 | f := store.IssueFilter{State: "open"} | 171 | f := store.IssueFilter{State: "open"} |
| 173 | fl, err := parseFlags(args, flagSpec{Values: []string{"--state", "--label", "--assignee", "--author", "--milestone", "--search"}, MaxPos: 1, Usage: usage}) | 172 | fl, err := parseFlags(args, flagSpec{Values: []string{"--state", "--label", "--assignee", "--author", "--milestone", "--search"}, MaxPos: 1, Usage: c.Cmd.Usage}) |
| 174 | if err != nil { | 173 | if err != nil { |
| 175 | return c.fail(protocol.ExitUsage, "%v", err) | 174 | return c.fail(protocol.ExitUsage, "%v", err) |
| 176 | } | 175 | } |
| @@ -186,7 +185,7 @@ func runIssueList(c *Ctx, args []string) int { | |||
| 186 | } | 185 | } |
| 187 | } | 186 | } |
| 188 | if path == "" || (f.State != "open" && f.State != "closed" && f.State != "all") { | 187 | if path == "" || (f.State != "open" && f.State != "closed" && f.State != "all") { |
| 189 | return c.fail(protocol.ExitUsage, usage) | 188 | return c.usage() |
| 190 | } | 189 | } |
| 191 | repo, code := resolveRepo(c, path, policy.CanRead) | 190 | repo, code := resolveRepo(c, path, policy.CanRead) |
| 192 | if code >= 0 { | 191 | if code >= 0 { |
| @@ -217,7 +216,7 @@ func runIssueShow(c *Ctx, args []string) int { | |||
| 217 | return code | 216 | return code |
| 218 | } | 217 | } |
| 219 | if len(args) != 2 { | 218 | if len(args) != 2 { |
| 220 | return c.fail(protocol.ExitUsage, "usage: issue show <owner/name> <n>") | 219 | return c.usage() |
| 221 | } | 220 | } |
| 222 | comments, err := c.Store.ListIssueComments(issue.ID) | 221 | comments, err := c.Store.ListIssueComments(issue.ID) |
| 223 | if err != nil { | 222 | if err != nil { |
| @@ -265,7 +264,7 @@ func setIssueState(c *Ctx, args []string, state string) int { | |||
| 265 | return code | 264 | return code |
| 266 | } | 265 | } |
| 267 | if len(args) != 2 { | 266 | if len(args) != 2 { |
| 268 | return c.fail(protocol.ExitUsage, "usage: issue %s <owner/name> <n>", state) | 267 | return c.usage() |
| 269 | } | 268 | } |
| 270 | if code := authorOrWrite(c, repo, issue.Author, map[string]string{"open": "reopen", "closed": "close"}[state]+" this issue"); code >= 0 { | 269 | if code := authorOrWrite(c, repo, issue.Author, map[string]string{"open": "reopen", "closed": "close"}[state]+" this issue"); code >= 0 { |
| 271 | return code | 270 | return code |
| @@ -312,7 +311,7 @@ func editText(c *Ctx, args []string, kind string) (rest []string, title, body, f | |||
| 312 | return nil, nil, nil, nil, c.failInput(err) | 311 | return nil, nil, nil, nil, c.failInput(err) |
| 313 | } | 312 | } |
| 314 | if !haveTitle && !haveBody && fmtName == "" { | 313 | if !haveTitle && !haveBody && fmtName == "" { |
| 315 | return nil, nil, nil, nil, c.fail(protocol.ExitUsage, "usage: %s edit <owner/name> <n> [--title <t>] [--body <b> | --file -] [--format md|org]", kind) | 314 | return nil, nil, nil, nil, c.usage() |
| 316 | } | 315 | } |
| 317 | if haveTitle { | 316 | if haveTitle { |
| 318 | if strings.TrimSpace(titleV) == "" { | 317 | if strings.TrimSpace(titleV) == "" { |
| @@ -378,7 +377,7 @@ func runIssueLabel(c *Ctx, args []string) int { | |||
| 378 | return c.failInput(err) | 377 | return c.failInput(err) |
| 379 | } | 378 | } |
| 380 | if len(adds)+len(removes) == 0 { | 379 | if len(adds)+len(removes) == 0 { |
| 381 | return c.fail(protocol.ExitUsage, "usage: issue label <owner/name> <n> [--add <l>]... [--remove <l>]...") | 380 | return c.usage() |
| 382 | } | 381 | } |
| 383 | repo, issue, code := issueRef(c, rest, policy.CanWrite) | 382 | repo, issue, code := issueRef(c, rest, policy.CanWrite) |
| 384 | if code >= 0 { | 383 | if code >= 0 { |
| @@ -417,7 +416,7 @@ func runIssueAssign(c *Ctx, args []string) int { | |||
| 417 | return c.failInput(err) | 416 | return c.failInput(err) |
| 418 | } | 417 | } |
| 419 | if len(adds)+len(removes) == 0 { | 418 | if len(adds)+len(removes) == 0 { |
| 420 | return c.fail(protocol.ExitUsage, "usage: issue assign <owner/name> <n> [--add <user>]... [--remove <user>]...") | 419 | return c.usage() |
| 421 | } | 420 | } |
| 422 | repo, issue, code := issueRef(c, rest, policy.CanWrite) | 421 | repo, issue, code := issueRef(c, rest, policy.CanWrite) |
| 423 | if code >= 0 { | 422 | if code >= 0 { |
internal/control/label.go +4 −5
| @@ -31,7 +31,7 @@ var labelColorPat = regexp.MustCompile(`^#?[0-9a-fA-F]{6}$`) | |||
| 31 | 31 | ||
| 32 | func runLabelList(c *Ctx, args []string) int { | 32 | func runLabelList(c *Ctx, args []string) int { |
| 33 | if len(args) != 1 { | 33 | if len(args) != 1 { |
| 34 | return c.fail(protocol.ExitUsage, "usage: label list <owner/name>") | 34 | return c.usage() |
| 35 | } | 35 | } |
| 36 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 36 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 37 | if code >= 0 { | 37 | if code >= 0 { |
| @@ -53,15 +53,14 @@ func runLabelList(c *Ctx, args []string) int { | |||
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | func runLabelSet(c *Ctx, args []string) int { | 55 | func runLabelSet(c *Ctx, args []string) int { |
| 56 | const usage = "usage: label set <owner/name> <label> [--color #rrggbb|'']" | 56 | f, err := parseFlags(args, flagSpec{Values: []string{"--color"}, MaxPos: -1, Usage: c.Cmd.Usage}) |
| 57 | f, err := parseFlags(args, flagSpec{Values: []string{"--color"}, MaxPos: -1, Usage: usage}) | ||
| 58 | if err != nil { | 57 | if err != nil { |
| 59 | return c.fail(protocol.ExitUsage, "%v", err) | 58 | return c.fail(protocol.ExitUsage, "%v", err) |
| 60 | } | 59 | } |
| 61 | rest := f.Pos | 60 | rest := f.Pos |
| 62 | color, colorSet := strings.ToLower(f.Value("--color")), f.Has("--color") | 61 | color, colorSet := strings.ToLower(f.Value("--color")), f.Has("--color") |
| 63 | if len(rest) != 2 { | 62 | if len(rest) != 2 { |
| 64 | return c.fail(protocol.ExitUsage, usage) | 63 | return c.usage() |
| 65 | } | 64 | } |
| 66 | if colorSet && color != "" { | 65 | if colorSet && color != "" { |
| 67 | if !labelColorPat.MatchString(color) { | 66 | if !labelColorPat.MatchString(color) { |
| @@ -103,7 +102,7 @@ func runLabelSet(c *Ctx, args []string) int { | |||
| 103 | 102 | ||
| 104 | func runLabelRemove(c *Ctx, args []string) int { | 103 | func runLabelRemove(c *Ctx, args []string) int { |
| 105 | if len(args) != 2 { | 104 | if len(args) != 2 { |
| 106 | return c.fail(protocol.ExitUsage, "usage: label remove <owner/name> <label>") | 105 | return c.usage() |
| 107 | } | 106 | } |
| 108 | repo, code := resolveRepo(c, args[0], policy.CanWrite) | 107 | repo, code := resolveRepo(c, args[0], policy.CanWrite) |
| 109 | if code >= 0 { | 108 | if code >= 0 { |
internal/control/migrate.go +1 −1
| @@ -81,7 +81,7 @@ type bundle struct { | |||
| 81 | 81 | ||
| 82 | func runAccountExport(c *Ctx, args []string) int { | 82 | func runAccountExport(c *Ctx, args []string) int { |
| 83 | if len(args) != 0 { | 83 | if len(args) != 0 { |
| 84 | return c.fail(protocol.ExitUsage, "usage: account export > bundle.json") | 84 | return c.usage() |
| 85 | } | 85 | } |
| 86 | b := bundle{Bundle: bundleVersion, Username: c.User.Username} | 86 | b := bundle{Bundle: bundleVersion, Username: c.User.Username} |
| 87 | b.Profile, _ = c.Store.OwnerProfile("user", c.User.ID) | 87 | b.Profile, _ = c.Store.OwnerProfile("user", c.User.ID) |
internal/control/milestone.go +6 −6
| @@ -48,7 +48,7 @@ func runMilestoneCreate(c *Ctx, args []string) int { | |||
| 48 | } | 48 | } |
| 49 | path, title, description, due := f.pos(0), f.pos(1), f.Value("--description"), f.Value("--due") | 49 | path, title, description, due := f.pos(0), f.pos(1), f.Value("--description"), f.Value("--due") |
| 50 | if path == "" || title == "" { | 50 | if path == "" || title == "" { |
| 51 | return c.fail(protocol.ExitUsage, "usage: milestone create <owner/name> <title> [--description <d>] [--due YYYY-MM-DD]") | 51 | return c.usage() |
| 52 | } | 52 | } |
| 53 | if due != "" && !duePat.MatchString(due) { | 53 | if due != "" && !duePat.MatchString(due) { |
| 54 | return c.fail(protocol.ExitUsage, "--due must be YYYY-MM-DD") | 54 | return c.fail(protocol.ExitUsage, "--due must be YYYY-MM-DD") |
| @@ -81,7 +81,7 @@ func runMilestoneList(c *Ctx, args []string) int { | |||
| 81 | state = f.Value("--state") | 81 | state = f.Value("--state") |
| 82 | } | 82 | } |
| 83 | if path == "" || (state != "open" && state != "closed" && state != "all") { | 83 | if path == "" || (state != "open" && state != "closed" && state != "all") { |
| 84 | return c.fail(protocol.ExitUsage, "usage: milestone list <owner/name> [--state open|closed|all]") | 84 | return c.usage() |
| 85 | } | 85 | } |
| 86 | repo, code := resolveRepo(c, path, policy.CanRead) | 86 | repo, code := resolveRepo(c, path, policy.CanRead) |
| 87 | if code >= 0 { | 87 | if code >= 0 { |
| @@ -137,7 +137,7 @@ func setMilestoneState(c *Ctx, args []string, state string) int { | |||
| 137 | verb = "reopen" | 137 | verb = "reopen" |
| 138 | } | 138 | } |
| 139 | if len(args) != 2 { | 139 | if len(args) != 2 { |
| 140 | return c.fail(protocol.ExitUsage, "usage: milestone %s <owner/name> <title>", verb) | 140 | return c.usage() |
| 141 | } | 141 | } |
| 142 | repo, code := resolveRepo(c, args[0], policy.CanWrite) | 142 | repo, code := resolveRepo(c, args[0], policy.CanWrite) |
| 143 | if code >= 0 { | 143 | if code >= 0 { |
| @@ -177,7 +177,7 @@ func runIssueMilestone(c *Ctx, args []string) int { | |||
| 177 | return code | 177 | return code |
| 178 | } | 178 | } |
| 179 | if len(args) != 3 { | 179 | if len(args) != 3 { |
| 180 | return c.fail(protocol.ExitUsage, "usage: issue milestone <owner/name> <n> <title|none>") | 180 | return c.usage() |
| 181 | } | 181 | } |
| 182 | return setItemMilestone(c, repo, "issue", issue.Number, args[2], func(id int64) error { | 182 | return setItemMilestone(c, repo, "issue", issue.Number, args[2], func(id int64) error { |
| 183 | return c.Store.SetIssueMilestone(issue.ID, id) | 183 | return c.Store.SetIssueMilestone(issue.ID, id) |
| @@ -193,7 +193,7 @@ func runMRMilestone(c *Ctx, args []string) int { | |||
| 193 | return code | 193 | return code |
| 194 | } | 194 | } |
| 195 | if len(args) != 3 { | 195 | if len(args) != 3 { |
| 196 | return c.fail(protocol.ExitUsage, "usage: mr milestone <owner/name> <n> <title|none>") | 196 | return c.usage() |
| 197 | } | 197 | } |
| 198 | return setItemMilestone(c, repo, "mr", mr.Number, args[2], func(id int64) error { | 198 | return setItemMilestone(c, repo, "mr", mr.Number, args[2], func(id int64) error { |
| 199 | return c.Store.SetMRMilestone(mr.ID, id) | 199 | return c.Store.SetMRMilestone(mr.ID, id) |
| @@ -232,7 +232,7 @@ func setItemMilestone(c *Ctx, repo store.Repo, noun string, number int64, title | |||
| 232 | // runIssueTemplates lists .gitbay/issue-template*.md at the default branch. | 232 | // runIssueTemplates lists .gitbay/issue-template*.md at the default branch. |
| 233 | func runIssueTemplates(c *Ctx, args []string) int { | 233 | func runIssueTemplates(c *Ctx, args []string) int { |
| 234 | if len(args) != 1 { | 234 | if len(args) != 1 { |
| 235 | return c.fail(protocol.ExitUsage, "usage: issue templates <owner/name>") | 235 | return c.usage() |
| 236 | } | 236 | } |
| 237 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 237 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 238 | if code >= 0 { | 238 | if code >= 0 { |
internal/control/mirrorcmd.go +4 −4
| @@ -39,7 +39,7 @@ func runMirrorAdd(c *Ctx, args []string) int { | |||
| 39 | path, urlArg := f.pos(0), f.pos(1) | 39 | path, urlArg := f.pos(0), f.pos(1) |
| 40 | direction, username, tokenStdin := f.Value("--direction"), f.Value("--username"), f.Has("--token-stdin") | 40 | direction, username, tokenStdin := f.Value("--direction"), f.Value("--username"), f.Has("--token-stdin") |
| 41 | if path == "" || urlArg == "" || (direction != "push" && direction != "pull") { | 41 | if path == "" || urlArg == "" || (direction != "push" && direction != "pull") { |
| 42 | return c.fail(protocol.ExitUsage, "usage: repo mirror add <owner/name> <https-url> --direction push|pull [--username <u>] [--token-stdin]") | 42 | return c.usage() |
| 43 | } | 43 | } |
| 44 | // The worker's git process dials this URL from the server: same SSRF | 44 | // The worker's git process dials this URL from the server: same SSRF |
| 45 | // surface as a webhook target, same rules. | 45 | // surface as a webhook target, same rules. |
| @@ -76,7 +76,7 @@ func runMirrorAdd(c *Ctx, args []string) int { | |||
| 76 | 76 | ||
| 77 | func runMirrorList(c *Ctx, args []string) int { | 77 | func runMirrorList(c *Ctx, args []string) int { |
| 78 | if len(args) != 1 { | 78 | if len(args) != 1 { |
| 79 | return c.fail(protocol.ExitUsage, "usage: repo mirror list <owner/name>") | 79 | return c.usage() |
| 80 | } | 80 | } |
| 81 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 81 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 82 | if code >= 0 { | 82 | if code >= 0 { |
| @@ -123,7 +123,7 @@ func orDash(s string) string { | |||
| 123 | 123 | ||
| 124 | func runMirrorRemove(c *Ctx, args []string) int { | 124 | func runMirrorRemove(c *Ctx, args []string) int { |
| 125 | if len(args) != 2 { | 125 | if len(args) != 2 { |
| 126 | return c.fail(protocol.ExitUsage, "usage: repo mirror remove <owner/name> <id>") | 126 | return c.usage() |
| 127 | } | 127 | } |
| 128 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 128 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 129 | if code >= 0 { | 129 | if code >= 0 { |
| @@ -146,7 +146,7 @@ func runMirrorRemove(c *Ctx, args []string) int { | |||
| 146 | 146 | ||
| 147 | func runMirrorSync(c *Ctx, args []string) int { | 147 | func runMirrorSync(c *Ctx, args []string) int { |
| 148 | if len(args) != 1 { | 148 | if len(args) != 1 { |
| 149 | return c.fail(protocol.ExitUsage, "usage: repo mirror sync <owner/name>") | 149 | return c.usage() |
| 150 | } | 150 | } |
| 151 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 151 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 152 | if code >= 0 { | 152 | if code >= 0 { |
internal/control/mr.go +19 −26
| @@ -104,7 +104,7 @@ func runRepoFork(c *Ctx, args []string) int { | |||
| 104 | } | 104 | } |
| 105 | path, name := f.pos(0), f.Value("--name") | 105 | path, name := f.pos(0), f.Value("--name") |
| 106 | if path == "" { | 106 | if path == "" { |
| 107 | return c.fail(protocol.ExitUsage, "usage: repo fork <owner/name> [--name <n>]") | 107 | return c.usage() |
| 108 | } | 108 | } |
| 109 | src, code := resolveRepo(c, path, policy.CanRead) | 109 | src, code := resolveRepo(c, path, policy.CanRead) |
| 110 | if code >= 0 { | 110 | if code >= 0 { |
| @@ -150,7 +150,7 @@ func runRepoFork(c *Ctx, args []string) int { | |||
| 150 | 150 | ||
| 151 | func runRequireApprovals(c *Ctx, args []string) int { | 151 | func runRequireApprovals(c *Ctx, args []string) int { |
| 152 | if len(args) != 2 { | 152 | if len(args) != 2 { |
| 153 | return c.fail(protocol.ExitUsage, "usage: repo settings require-approvals <owner/name> <n>") | 153 | return c.usage() |
| 154 | } | 154 | } |
| 155 | n, err := strconv.Atoi(args[1]) | 155 | n, err := strconv.Atoi(args[1]) |
| 156 | if err != nil || n < 0 || n > 20 { | 156 | if err != nil || n < 0 || n > 20 { |
| @@ -171,7 +171,7 @@ func runRequireApprovals(c *Ctx, args []string) int { | |||
| 171 | 171 | ||
| 172 | func runRequireResolved(c *Ctx, args []string) int { | 172 | func runRequireResolved(c *Ctx, args []string) int { |
| 173 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { | 173 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { |
| 174 | return c.fail(protocol.ExitUsage, "usage: repo settings require-resolved <owner/name> on|off") | 174 | return c.usage() |
| 175 | } | 175 | } |
| 176 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 176 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 177 | if code >= 0 { | 177 | if code >= 0 { |
| @@ -188,7 +188,7 @@ func runRequireResolved(c *Ctx, args []string) int { | |||
| 188 | 188 | ||
| 189 | func runRequireCodeowners(c *Ctx, args []string) int { | 189 | func runRequireCodeowners(c *Ctx, args []string) int { |
| 190 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { | 190 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { |
| 191 | return c.fail(protocol.ExitUsage, "usage: repo settings require-codeowners <owner/name> on|off") | 191 | return c.usage() |
| 192 | } | 192 | } |
| 193 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 193 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 194 | if code >= 0 { | 194 | if code >= 0 { |
| @@ -205,7 +205,7 @@ func runRequireCodeowners(c *Ctx, args []string) int { | |||
| 205 | 205 | ||
| 206 | func runRequireChecks(c *Ctx, args []string) int { | 206 | func runRequireChecks(c *Ctx, args []string) int { |
| 207 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { | 207 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { |
| 208 | return c.fail(protocol.ExitUsage, "usage: repo settings require-checks <owner/name> on|off") | 208 | return c.usage() |
| 209 | } | 209 | } |
| 210 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 210 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 211 | if code >= 0 { | 211 | if code >= 0 { |
| @@ -222,7 +222,7 @@ func runRequireChecks(c *Ctx, args []string) int { | |||
| 222 | 222 | ||
| 223 | func runRequireMR(c *Ctx, args []string) int { | 223 | func runRequireMR(c *Ctx, args []string) int { |
| 224 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { | 224 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { |
| 225 | return c.fail(protocol.ExitUsage, "usage: repo settings require-mr <owner/name> on|off") | 225 | return c.usage() |
| 226 | } | 226 | } |
| 227 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 227 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 228 | if code >= 0 { | 228 | if code >= 0 { |
| @@ -239,7 +239,7 @@ func runRequireMR(c *Ctx, args []string) int { | |||
| 239 | 239 | ||
| 240 | func runRequireSigned(c *Ctx, args []string) int { | 240 | func runRequireSigned(c *Ctx, args []string) int { |
| 241 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { | 241 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { |
| 242 | return c.fail(protocol.ExitUsage, "usage: repo settings require-signed <owner/name> on|off") | 242 | return c.usage() |
| 243 | } | 243 | } |
| 244 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 244 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 245 | if code >= 0 { | 245 | if code >= 0 { |
| @@ -282,7 +282,7 @@ func runMRCreate(c *Ctx, args []string) int { | |||
| 282 | path, source, target := f.pos(0), f.Value("--source"), f.Value("--target") | 282 | path, source, target := f.pos(0), f.Value("--source"), f.Value("--target") |
| 283 | title, body, file, format := f.Value("--title"), f.Value("--body"), f.Value("--file"), f.Value("--format") | 283 | title, body, file, format := f.Value("--title"), f.Value("--body"), f.Value("--file"), f.Value("--format") |
| 284 | if path == "" || source == "" || title == "" { | 284 | if path == "" || source == "" || title == "" { |
| 285 | return c.fail(protocol.ExitUsage, "usage: mr create <target owner/name> --source [owner/name:]<branch> --target <branch> --title <t> [--draft]") | 285 | return c.usage() |
| 286 | } | 286 | } |
| 287 | fmtName, err := markupFormat(format) | 287 | fmtName, err := markupFormat(format) |
| 288 | if err != nil { | 288 | if err != nil { |
| @@ -441,9 +441,8 @@ func runMRList(c *Ctx, args []string) int { | |||
| 441 | if code >= 0 { | 441 | if code >= 0 { |
| 442 | return code | 442 | return code |
| 443 | } | 443 | } |
| 444 | const usage = "usage: mr list <owner/name> [--state open|merged|closed|source_gone|all] [--author <user>] [--milestone <title>|none] [--search <text>] [--limit <n>] [--cursor <c>]" | ||
| 445 | f := store.MRFilter{State: "open"} | 444 | f := store.MRFilter{State: "open"} |
| 446 | fl, err := parseFlags(args, flagSpec{Values: []string{"--state", "--author", "--milestone", "--search"}, MaxPos: 1, Usage: usage}) | 445 | fl, err := parseFlags(args, flagSpec{Values: []string{"--state", "--author", "--milestone", "--search"}, MaxPos: 1, Usage: c.Cmd.Usage}) |
| 447 | if err != nil { | 446 | if err != nil { |
| 448 | return c.fail(protocol.ExitUsage, "%v", err) | 447 | return c.fail(protocol.ExitUsage, "%v", err) |
| 449 | } | 448 | } |
| @@ -460,7 +459,7 @@ func runMRList(c *Ctx, args []string) int { | |||
| 460 | } | 459 | } |
| 461 | valid := map[string]bool{"open": true, "merged": true, "closed": true, "source_gone": true, "all": true} | 460 | valid := map[string]bool{"open": true, "merged": true, "closed": true, "source_gone": true, "all": true} |
| 462 | if path == "" || !valid[f.State] { | 461 | if path == "" || !valid[f.State] { |
| 463 | return c.fail(protocol.ExitUsage, usage) | 462 | return c.usage() |
| 464 | } | 463 | } |
| 465 | repo, code := resolveRepo(c, path, policy.CanRead) | 464 | repo, code := resolveRepo(c, path, policy.CanRead) |
| 466 | if code >= 0 { | 465 | if code >= 0 { |
| @@ -510,7 +509,7 @@ func runMRShow(c *Ctx, args []string) int { | |||
| 510 | return code | 509 | return code |
| 511 | } | 510 | } |
| 512 | if len(args) != 2 { | 511 | if len(args) != 2 { |
| 513 | return c.fail(protocol.ExitUsage, "usage: mr show <owner/name> <n>") | 512 | return c.usage() |
| 514 | } | 513 | } |
| 515 | comments, err := c.Store.ListMRComments(mr.ID) | 514 | comments, err := c.Store.ListMRComments(mr.ID) |
| 516 | if err != nil { | 515 | if err != nil { |
| @@ -701,7 +700,7 @@ func runMREdit(c *Ctx, args []string) int { | |||
| 701 | // same repository. | 700 | // same repository. |
| 702 | func runMRRetarget(c *Ctx, args []string) int { | 701 | func runMRRetarget(c *Ctx, args []string) int { |
| 703 | if len(args) != 3 { | 702 | if len(args) != 3 { |
| 704 | return c.fail(protocol.ExitUsage, "usage: mr retarget <owner/name> <n> <branch>") | 703 | return c.usage() |
| 705 | } | 704 | } |
| 706 | repo, mr, code := mrRef(c, args[:2], policy.CanRead) | 705 | repo, mr, code := mrRef(c, args[:2], policy.CanRead) |
| 707 | if code >= 0 { | 706 | if code >= 0 { |
| @@ -779,12 +778,11 @@ func runMRReview(c *Ctx, args []string) int { | |||
| 779 | rest = append(rest, a) | 778 | rest = append(rest, a) |
| 780 | } | 779 | } |
| 781 | } | 780 | } |
| 782 | const usage = "mr review <owner/name> <n> --approve|--request-changes|--comment|--discard" | ||
| 783 | if discard && verdict != "" { | 781 | if discard && verdict != "" { |
| 784 | return c.fail(protocol.ExitUsage, "--discard throws the batch away; it takes no verdict") | 782 | return c.fail(protocol.ExitUsage, "--discard throws the batch away; it takes no verdict") |
| 785 | } | 783 | } |
| 786 | if verdict == "" && !discard { | 784 | if verdict == "" && !discard { |
| 787 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 785 | return c.usage() |
| 788 | } | 786 | } |
| 789 | repo, mr, code := mrRef(c, rest, policy.CanRead) | 787 | repo, mr, code := mrRef(c, rest, policy.CanRead) |
| 790 | if code >= 0 { | 788 | if code >= 0 { |
| @@ -848,7 +846,7 @@ func runMRReviewRequest(c *Ctx, args []string) int { | |||
| 848 | return c.failInput(err) | 846 | return c.failInput(err) |
| 849 | } | 847 | } |
| 850 | if len(adds)+len(removes) == 0 { | 848 | if len(adds)+len(removes) == 0 { |
| 851 | return c.fail(protocol.ExitUsage, "usage: mr review request <owner/name> <n> [--add <user>]... [--remove <user>]...") | 849 | return c.usage() |
| 852 | } | 850 | } |
| 853 | repo, mr, code := mrRef(c, rest, policy.CanWrite) | 851 | repo, mr, code := mrRef(c, rest, policy.CanWrite) |
| 854 | if code >= 0 { | 852 | if code >= 0 { |
| @@ -1404,10 +1402,6 @@ func runMRDraft(c *Ctx, args []string) int { return setMRDraft(c, args, true) } | |||
| 1404 | func runMRReady(c *Ctx, args []string) int { return setMRDraft(c, args, false) } | 1402 | func runMRReady(c *Ctx, args []string) int { return setMRDraft(c, args, false) } |
| 1405 | 1403 | ||
| 1406 | func setMRDraft(c *Ctx, args []string, draft bool) int { | 1404 | func setMRDraft(c *Ctx, args []string, draft bool) int { |
| 1407 | verb := "ready" | ||
| 1408 | if draft { | ||
| 1409 | verb = "draft" | ||
| 1410 | } | ||
| 1411 | repo, mr, code := mrRef(c, args, policy.CanRead) | 1405 | repo, mr, code := mrRef(c, args, policy.CanRead) |
| 1412 | if code >= 0 { | 1406 | if code >= 0 { |
| 1413 | return code | 1407 | return code |
| @@ -1416,7 +1410,7 @@ func setMRDraft(c *Ctx, args []string, draft bool) int { | |||
| 1416 | return code | 1410 | return code |
| 1417 | } | 1411 | } |
| 1418 | if len(args) != 2 { | 1412 | if len(args) != 2 { |
| 1419 | return c.fail(protocol.ExitUsage, "usage: mr %s <owner/name> <n>", verb) | 1413 | return c.usage() |
| 1420 | } | 1414 | } |
| 1421 | if code := authorOrWrite(c, repo, mr.Author, "change this merge request"); code >= 0 { | 1415 | if code := authorOrWrite(c, repo, mr.Author, "change this merge request"); code >= 0 { |
| 1422 | return code | 1416 | return code |
| @@ -1471,7 +1465,7 @@ func runMRClose(c *Ctx, args []string) int { | |||
| 1471 | return code | 1465 | return code |
| 1472 | } | 1466 | } |
| 1473 | if len(args) != 2 { | 1467 | if len(args) != 2 { |
| 1474 | return c.fail(protocol.ExitUsage, "usage: mr close <owner/name> <n>") | 1468 | return c.usage() |
| 1475 | } | 1469 | } |
| 1476 | if code := authorOrWrite(c, repo, mr.Author, "close this merge request"); code >= 0 { | 1470 | if code := authorOrWrite(c, repo, mr.Author, "close this merge request"); code >= 0 { |
| 1477 | return code | 1471 | return code |
| @@ -1532,7 +1526,7 @@ func runMRRevisions(c *Ctx, args []string) int { | |||
| 1532 | return code | 1526 | return code |
| 1533 | } | 1527 | } |
| 1534 | if len(args) != 2 { | 1528 | if len(args) != 2 { |
| 1535 | return c.fail(protocol.ExitUsage, "usage: mr revisions <owner/name> <n>") | 1529 | return c.usage() |
| 1536 | } | 1530 | } |
| 1537 | revs, err := mrRevisions(c, mr) | 1531 | revs, err := mrRevisions(c, mr) |
| 1538 | if err != nil { | 1532 | if err != nil { |
| @@ -1554,8 +1548,7 @@ func runMRRevisions(c *Ctx, args []string) int { | |||
| 1554 | } | 1548 | } |
| 1555 | 1549 | ||
| 1556 | func runMRRangeDiff(c *Ctx, args []string) int { | 1550 | func runMRRangeDiff(c *Ctx, args []string) int { |
| 1557 | const usage = "mr range-diff <owner/name> <n> [--from <sha>] [--to <sha>]" | 1551 | f, err := parseFlags(args, flagSpec{Values: []string{"--from", "--to"}, MaxPos: 2, Usage: c.Cmd.Usage}) |
| 1558 | f, err := parseFlags(args, flagSpec{Values: []string{"--from", "--to"}, MaxPos: 2, Usage: usage}) | ||
| 1559 | if err != nil { | 1552 | if err != nil { |
| 1560 | return c.fail(protocol.ExitUsage, "%v", err) | 1553 | return c.fail(protocol.ExitUsage, "%v", err) |
| 1561 | } | 1554 | } |
| @@ -1564,7 +1557,7 @@ func runMRRangeDiff(c *Ctx, args []string) int { | |||
| 1564 | return code | 1557 | return code |
| 1565 | } | 1558 | } |
| 1566 | if len(f.Pos) != 2 { | 1559 | if len(f.Pos) != 2 { |
| 1567 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 1560 | return c.usage() |
| 1568 | } | 1561 | } |
| 1569 | revs, err := mrRevisions(c, mr) | 1562 | revs, err := mrRevisions(c, mr) |
| 1570 | if err != nil { | 1563 | if err != nil { |
internal/control/notifications.go +7 −9
| @@ -168,7 +168,7 @@ func emitNotificationSettings(c *Ctx) int { | |||
| 168 | 168 | ||
| 169 | func runNotificationsSettingsShow(c *Ctx, args []string) int { | 169 | func runNotificationsSettingsShow(c *Ctx, args []string) int { |
| 170 | if len(args) != 0 { | 170 | if len(args) != 0 { |
| 171 | return c.fail(protocol.ExitUsage, "usage: notifications settings show") | 171 | return c.usage() |
| 172 | } | 172 | } |
| 173 | return emitNotificationSettings(c) | 173 | return emitNotificationSettings(c) |
| 174 | } | 174 | } |
| @@ -177,7 +177,7 @@ func runNotificationsSettingsShow(c *Ctx, args []string) int { | |||
| 177 | // inbox is filed either way, the mail half consults it (#194). | 177 | // inbox is filed either way, the mail half consults it (#194). |
| 178 | func runNotificationsSettingsMail(c *Ctx, args []string) int { | 178 | func runNotificationsSettingsMail(c *Ctx, args []string) int { |
| 179 | if len(args) != 1 || (args[0] != "on" && args[0] != "off") { | 179 | if len(args) != 1 || (args[0] != "on" && args[0] != "off") { |
| 180 | return c.fail(protocol.ExitUsage, "usage: notifications settings mail on|off") | 180 | return c.usage() |
| 181 | } | 181 | } |
| 182 | if err := c.Store.SetMailEnabled(c.User.ID, args[0] == "on"); err != nil { | 182 | if err := c.Store.SetMailEnabled(c.User.ID, args[0] == "on"); err != nil { |
| 183 | return c.fail(protocol.ExitFailure, "%v", err) | 183 | return c.fail(protocol.ExitFailure, "%v", err) |
| @@ -190,7 +190,7 @@ func runNotificationsSettingsMail(c *Ctx, args []string) int { | |||
| 190 | // delivered, so a grant or a revoke needs no watch row of its own (#194). | 190 | // delivered, so a grant or a revoke needs no watch row of its own (#194). |
| 191 | func runNotificationsSettingsWatch(c *Ctx, args []string) int { | 191 | func runNotificationsSettingsWatch(c *Ctx, args []string) int { |
| 192 | if len(args) != 1 || (args[0] != "on" && args[0] != "off") { | 192 | if len(args) != 1 || (args[0] != "on" && args[0] != "off") { |
| 193 | return c.fail(protocol.ExitUsage, "usage: notifications settings watch on|off") | 193 | return c.usage() |
| 194 | } | 194 | } |
| 195 | if err := c.Store.SetWatchEnabled(c.User.ID, args[0] == "on"); err != nil { | 195 | if err := c.Store.SetWatchEnabled(c.User.ID, args[0] == "on"); err != nil { |
| 196 | return c.fail(protocol.ExitFailure, "%v", err) | 196 | return c.fail(protocol.ExitFailure, "%v", err) |
| @@ -202,12 +202,11 @@ func runNotificationsSettingsWatch(c *Ctx, args []string) int { | |||
| 202 | const noticesDefaultLimit = 50 | 202 | const noticesDefaultLimit = 50 |
| 203 | 203 | ||
| 204 | func runNotificationsList(c *Ctx, args []string) int { | 204 | func runNotificationsList(c *Ctx, args []string) int { |
| 205 | const usage = "notifications list [--all] [--limit <n>] [--cursor <c>]" | ||
| 206 | rest, p, code := parsePageFlags(c, args, "notifications", true) | 205 | rest, p, code := parsePageFlags(c, args, "notifications", true) |
| 207 | if code >= 0 { | 206 | if code >= 0 { |
| 208 | return code | 207 | return code |
| 209 | } | 208 | } |
| 210 | fl, err := parseFlags(rest, flagSpec{Bools: []string{"--all"}, Usage: usage}) | 209 | fl, err := parseFlags(rest, flagSpec{Bools: []string{"--all"}, Usage: c.Cmd.Usage}) |
| 211 | if err != nil { | 210 | if err != nil { |
| 212 | return c.fail(protocol.ExitUsage, "%v", err) | 211 | return c.fail(protocol.ExitUsage, "%v", err) |
| 213 | } | 212 | } |
| @@ -249,15 +248,14 @@ func runNotificationsList(c *Ctx, args []string) int { | |||
| 249 | } | 248 | } |
| 250 | 249 | ||
| 251 | func runNotificationsRead(c *Ctx, args []string) int { | 250 | func runNotificationsRead(c *Ctx, args []string) int { |
| 252 | const usage = "notifications read <id>... | --all" | 251 | fl, err := parseFlags(args, flagSpec{Bools: []string{"--all"}, MaxPos: -1, Usage: c.Cmd.Usage}) |
| 253 | fl, err := parseFlags(args, flagSpec{Bools: []string{"--all"}, MaxPos: -1, Usage: usage}) | ||
| 254 | if err != nil { | 252 | if err != nil { |
| 255 | return c.fail(protocol.ExitUsage, "%v", err) | 253 | return c.fail(protocol.ExitUsage, "%v", err) |
| 256 | } | 254 | } |
| 257 | // --all and a list of ids are two ways of saying which rows: taking | 255 | // --all and a list of ids are two ways of saying which rows: taking |
| 258 | // both would leave which one won unstated. | 256 | // both would leave which one won unstated. |
| 259 | if fl.Has("--all") == (len(fl.Pos) > 0) { | 257 | if fl.Has("--all") == (len(fl.Pos) > 0) { |
| 260 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 258 | return c.usage() |
| 261 | } | 259 | } |
| 262 | var ids []int64 | 260 | var ids []int64 |
| 263 | for _, a := range fl.Pos { | 261 | for _, a := range fl.Pos { |
| @@ -285,7 +283,7 @@ func runRepoUnwatch(c *Ctx, args []string) int { return setWatch(c, args, "unwat | |||
| 285 | // same way. | 283 | // same way. |
| 286 | func setWatch(c *Ctx, args []string, verb, state string) int { | 284 | func setWatch(c *Ctx, args []string, verb, state string) int { |
| 287 | if len(args) != 1 { | 285 | if len(args) != 1 { |
| 288 | return c.fail(protocol.ExitUsage, "usage: repo %s <owner/name>", verb) | 286 | return c.usage() |
| 289 | } | 287 | } |
| 290 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 288 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 291 | if code >= 0 { | 289 | if code >= 0 { |
internal/control/org.go +9 −9
| @@ -53,14 +53,14 @@ func orgAdmin(c *Ctx, name string) (store.Org, int) { | |||
| 53 | return org, c.fail(protocol.ExitFailure, "%v", err) | 53 | return org, c.fail(protocol.ExitFailure, "%v", err) |
| 54 | } | 54 | } |
| 55 | if role != "admin" { | 55 | if role != "admin" { |
| 56 | return org, c.fail(protocol.ExitDenied, "only admins of %s can do that", name) | 56 | return org, c.fail(protocol.ExitDenied, "only admins of %s can do that; ask one", name) |
| 57 | } | 57 | } |
| 58 | return org, -1 | 58 | return org, -1 |
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | func runOrgCreate(c *Ctx, args []string) int { | 61 | func runOrgCreate(c *Ctx, args []string) int { |
| 62 | if len(args) != 1 { | 62 | if len(args) != 1 { |
| 63 | return c.fail(protocol.ExitUsage, "usage: org create <name>") | 63 | return c.usage() |
| 64 | } | 64 | } |
| 65 | if err := policy.ValidateOwnerName(args[0]); err != nil { | 65 | if err := policy.ValidateOwnerName(args[0]); err != nil { |
| 66 | return c.failInput(err) | 66 | return c.failInput(err) |
| @@ -95,7 +95,7 @@ func runOrgList(c *Ctx, args []string) int { | |||
| 95 | 95 | ||
| 96 | func runOrgShow(c *Ctx, args []string) int { | 96 | func runOrgShow(c *Ctx, args []string) int { |
| 97 | if len(args) != 1 { | 97 | if len(args) != 1 { |
| 98 | return c.fail(protocol.ExitUsage, "usage: org show <name>") | 98 | return c.usage() |
| 99 | } | 99 | } |
| 100 | org, err := c.Store.OrgByName(args[0]) | 100 | org, err := c.Store.OrgByName(args[0]) |
| 101 | if errors.Is(err, store.ErrNotFound) { | 101 | if errors.Is(err, store.ErrNotFound) { |
| @@ -130,7 +130,7 @@ func runOrgShow(c *Ctx, args []string) int { | |||
| 130 | 130 | ||
| 131 | func runOrgRename(c *Ctx, args []string) int { | 131 | func runOrgRename(c *Ctx, args []string) int { |
| 132 | if len(args) != 2 { | 132 | if len(args) != 2 { |
| 133 | return c.fail(protocol.ExitUsage, "usage: org rename <old> <new>") | 133 | return c.usage() |
| 134 | } | 134 | } |
| 135 | org, code := orgAdmin(c, args[0]) | 135 | org, code := orgAdmin(c, args[0]) |
| 136 | if code >= 0 { | 136 | if code >= 0 { |
| @@ -170,11 +170,11 @@ func runOrgDelete(c *Ctx, args []string) int { | |||
| 170 | } else if name == "" { | 170 | } else if name == "" { |
| 171 | name = a | 171 | name = a |
| 172 | } else { | 172 | } else { |
| 173 | return c.fail(protocol.ExitUsage, "usage: org delete <name> --yes") | 173 | return c.usage() |
| 174 | } | 174 | } |
| 175 | } | 175 | } |
| 176 | if name == "" { | 176 | if name == "" { |
| 177 | return c.fail(protocol.ExitUsage, "usage: org delete <name> --yes") | 177 | return c.usage() |
| 178 | } | 178 | } |
| 179 | org, code := orgAdmin(c, name) | 179 | org, code := orgAdmin(c, name) |
| 180 | if code >= 0 { | 180 | if code >= 0 { |
| @@ -201,7 +201,7 @@ func runOrgMembersAdd(c *Ctx, args []string) int { | |||
| 201 | role = f.Value("--role") | 201 | role = f.Value("--role") |
| 202 | } | 202 | } |
| 203 | if len(rest) != 2 || (role != "member" && role != "admin") { | 203 | if len(rest) != 2 || (role != "member" && role != "admin") { |
| 204 | return c.fail(protocol.ExitUsage, "usage: org members add <org> <user> [--role member|admin]") | 204 | return c.usage() |
| 205 | } | 205 | } |
| 206 | org, code := orgAdmin(c, rest[0]) | 206 | org, code := orgAdmin(c, rest[0]) |
| 207 | if code >= 0 { | 207 | if code >= 0 { |
| @@ -224,7 +224,7 @@ func runOrgMembersAdd(c *Ctx, args []string) int { | |||
| 224 | 224 | ||
| 225 | func runOrgMembersRemove(c *Ctx, args []string) int { | 225 | func runOrgMembersRemove(c *Ctx, args []string) int { |
| 226 | if len(args) != 2 { | 226 | if len(args) != 2 { |
| 227 | return c.fail(protocol.ExitUsage, "usage: org members remove <org> <user>") | 227 | return c.usage() |
| 228 | } | 228 | } |
| 229 | org, code := orgAdmin(c, args[0]) | 229 | org, code := orgAdmin(c, args[0]) |
| 230 | if code >= 0 { | 230 | if code >= 0 { |
| @@ -250,7 +250,7 @@ func runOrgMembersRemove(c *Ctx, args []string) int { | |||
| 250 | 250 | ||
| 251 | func runOrgMembersList(c *Ctx, args []string) int { | 251 | func runOrgMembersList(c *Ctx, args []string) int { |
| 252 | if len(args) != 1 { | 252 | if len(args) != 1 { |
| 253 | return c.fail(protocol.ExitUsage, "usage: org members list <org>") | 253 | return c.usage() |
| 254 | } | 254 | } |
| 255 | return runOrgShow(c, args) | 255 | return runOrgShow(c, args) |
| 256 | } | 256 | } |
internal/control/orglabel.go +9 −11
| @@ -56,21 +56,20 @@ func orgReader(c *Ctx, name string) (store.Org, []int64, int) { | |||
| 56 | return org, nil, c.fail(protocol.ExitFailure, "%v", err) | 56 | return org, nil, c.fail(protocol.ExitFailure, "%v", err) |
| 57 | } | 57 | } |
| 58 | if role == "" && len(readable) == 0 { | 58 | if role == "" && len(readable) == 0 { |
| 59 | return org, nil, c.fail(protocol.ExitDenied, "labels and milestones of %s are visible to its members", name) | 59 | return org, nil, c.fail(protocol.ExitDenied, "labels and milestones of %s are visible to its members; ask an admin to add you", name) |
| 60 | } | 60 | } |
| 61 | return org, readable, -1 | 61 | return org, readable, -1 |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | func runOrgLabelSet(c *Ctx, args []string) int { | 64 | func runOrgLabelSet(c *Ctx, args []string) int { |
| 65 | const usage = "usage: org label set <org> <label> [--color rrggbb|'']" | 65 | f, err := parseFlags(args, flagSpec{Values: []string{"--color"}, MaxPos: 2, Usage: c.Cmd.Usage}) |
| 66 | f, err := parseFlags(args, flagSpec{Values: []string{"--color"}, MaxPos: 2, Usage: usage}) | ||
| 67 | if err != nil { | 66 | if err != nil { |
| 68 | return c.fail(protocol.ExitUsage, "%v", err) | 67 | return c.fail(protocol.ExitUsage, "%v", err) |
| 69 | } | 68 | } |
| 70 | orgName, name := f.pos(0), f.pos(1) | 69 | orgName, name := f.pos(0), f.pos(1) |
| 71 | color, colorSet := strings.ToLower(f.Value("--color")), f.Has("--color") | 70 | color, colorSet := strings.ToLower(f.Value("--color")), f.Has("--color") |
| 72 | if orgName == "" || name == "" { | 71 | if orgName == "" || name == "" { |
| 73 | return c.fail(protocol.ExitUsage, usage) | 72 | return c.usage() |
| 74 | } | 73 | } |
| 75 | if name == "" || len(name) > 50 { | 74 | if name == "" || len(name) > 50 { |
| 76 | return c.fail(protocol.ExitUsage, "a label is 1 to 50 characters") | 75 | return c.fail(protocol.ExitUsage, "a label is 1 to 50 characters") |
| @@ -118,7 +117,7 @@ func runOrgLabelSet(c *Ctx, args []string) int { | |||
| 118 | 117 | ||
| 119 | func runOrgLabelList(c *Ctx, args []string) int { | 118 | func runOrgLabelList(c *Ctx, args []string) int { |
| 120 | if len(args) != 1 { | 119 | if len(args) != 1 { |
| 121 | return c.fail(protocol.ExitUsage, "usage: org label list <org>") | 120 | return c.usage() |
| 122 | } | 121 | } |
| 123 | org, readable, code := orgReader(c, args[0]) | 122 | org, readable, code := orgReader(c, args[0]) |
| 124 | if code >= 0 { | 123 | if code >= 0 { |
| @@ -137,7 +136,7 @@ func runOrgLabelList(c *Ctx, args []string) int { | |||
| 137 | 136 | ||
| 138 | func runOrgLabelRemove(c *Ctx, args []string) int { | 137 | func runOrgLabelRemove(c *Ctx, args []string) int { |
| 139 | if len(args) != 2 { | 138 | if len(args) != 2 { |
| 140 | return c.fail(protocol.ExitUsage, "usage: org label remove <org> <label>") | 139 | return c.usage() |
| 141 | } | 140 | } |
| 142 | org, code := orgAdmin(c, args[0]) | 141 | org, code := orgAdmin(c, args[0]) |
| 143 | if code >= 0 { | 142 | if code >= 0 { |
| @@ -155,14 +154,13 @@ func runOrgLabelRemove(c *Ctx, args []string) int { | |||
| 155 | } | 154 | } |
| 156 | 155 | ||
| 157 | func runOrgMilestoneCreate(c *Ctx, args []string) int { | 156 | func runOrgMilestoneCreate(c *Ctx, args []string) int { |
| 158 | const usage = "usage: org milestone create <org> <title> [--description <d>] [--due YYYY-MM-DD]" | 157 | f, err := parseFlags(args, flagSpec{Values: []string{"--description", "--due"}, MaxPos: 2, Usage: c.Cmd.Usage}) |
| 159 | f, err := parseFlags(args, flagSpec{Values: []string{"--description", "--due"}, MaxPos: 2, Usage: usage}) | ||
| 160 | if err != nil { | 158 | if err != nil { |
| 161 | return c.fail(protocol.ExitUsage, "%v", err) | 159 | return c.fail(protocol.ExitUsage, "%v", err) |
| 162 | } | 160 | } |
| 163 | orgName, title, description, due := f.pos(0), f.pos(1), f.Value("--description"), f.Value("--due") | 161 | orgName, title, description, due := f.pos(0), f.pos(1), f.Value("--description"), f.Value("--due") |
| 164 | if orgName == "" || title == "" { | 162 | if orgName == "" || title == "" { |
| 165 | return c.fail(protocol.ExitUsage, usage) | 163 | return c.usage() |
| 166 | } | 164 | } |
| 167 | if due != "" && !duePat.MatchString(due) { | 165 | if due != "" && !duePat.MatchString(due) { |
| 168 | return c.fail(protocol.ExitUsage, "--due must be YYYY-MM-DD") | 166 | return c.fail(protocol.ExitUsage, "--due must be YYYY-MM-DD") |
| @@ -197,7 +195,7 @@ func runOrgMilestoneList(c *Ctx, args []string) int { | |||
| 197 | state = f.Value("--state") | 195 | state = f.Value("--state") |
| 198 | } | 196 | } |
| 199 | if orgName == "" || (state != "open" && state != "closed" && state != "all") { | 197 | if orgName == "" || (state != "open" && state != "closed" && state != "all") { |
| 200 | return c.fail(protocol.ExitUsage, "usage: org milestone list <org> [--state open|closed|all]") | 198 | return c.usage() |
| 201 | } | 199 | } |
| 202 | org, readable, code := orgReader(c, orgName) | 200 | org, readable, code := orgReader(c, orgName) |
| 203 | if code >= 0 { | 201 | if code >= 0 { |
| @@ -219,7 +217,7 @@ func setOrgMilestoneState(c *Ctx, args []string, state string) int { | |||
| 219 | verb = "reopen" | 217 | verb = "reopen" |
| 220 | } | 218 | } |
| 221 | if len(args) != 2 { | 219 | if len(args) != 2 { |
| 222 | return c.fail(protocol.ExitUsage, "usage: org milestone %s <org> <title>", verb) | 220 | return c.usage() |
| 223 | } | 221 | } |
| 224 | org, code := orgAdmin(c, args[0]) | 222 | org, code := orgAdmin(c, args[0]) |
| 225 | if code >= 0 { | 223 | if code >= 0 { |
internal/control/pagescmd.go +4 −4
| @@ -70,7 +70,7 @@ func challengeRecord(domain, token string) (name, value string) { | |||
| 70 | 70 | ||
| 71 | func runDomainAdd(c *Ctx, args []string) int { | 71 | func runDomainAdd(c *Ctx, args []string) int { |
| 72 | if len(args) != 2 { | 72 | if len(args) != 2 { |
| 73 | return c.fail(protocol.ExitUsage, "usage: repo domain add <owner/name> <domain>") | 73 | return c.usage() |
| 74 | } | 74 | } |
| 75 | domain := strings.ToLower(args[1]) | 75 | domain := strings.ToLower(args[1]) |
| 76 | if err := validatePageDomain(c, domain); err != nil { | 76 | if err := validatePageDomain(c, domain); err != nil { |
| @@ -121,7 +121,7 @@ func lookupTXT(name string) ([]string, error) { | |||
| 121 | 121 | ||
| 122 | func runDomainVerify(c *Ctx, args []string) int { | 122 | func runDomainVerify(c *Ctx, args []string) int { |
| 123 | if len(args) != 2 { | 123 | if len(args) != 2 { |
| 124 | return c.fail(protocol.ExitUsage, "usage: repo domain verify <owner/name> <domain>") | 124 | return c.usage() |
| 125 | } | 125 | } |
| 126 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 126 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 127 | if code >= 0 { | 127 | if code >= 0 { |
| @@ -167,7 +167,7 @@ func runDomainVerify(c *Ctx, args []string) int { | |||
| 167 | 167 | ||
| 168 | func runDomainRemove(c *Ctx, args []string) int { | 168 | func runDomainRemove(c *Ctx, args []string) int { |
| 169 | if len(args) != 2 { | 169 | if len(args) != 2 { |
| 170 | return c.fail(protocol.ExitUsage, "usage: repo domain remove <owner/name> <domain>") | 170 | return c.usage() |
| 171 | } | 171 | } |
| 172 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 172 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 173 | if code >= 0 { | 173 | if code >= 0 { |
| @@ -187,7 +187,7 @@ func runDomainRemove(c *Ctx, args []string) int { | |||
| 187 | 187 | ||
| 188 | func runDomainList(c *Ctx, args []string) int { | 188 | func runDomainList(c *Ctx, args []string) int { |
| 189 | if len(args) != 1 { | 189 | if len(args) != 1 { |
| 190 | return c.fail(protocol.ExitUsage, "usage: repo domain list <owner/name>") | 190 | return c.usage() |
| 191 | } | 191 | } |
| 192 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 192 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 193 | if code >= 0 { | 193 | if code >= 0 { |
internal/control/profile.go +3 −5
| @@ -257,7 +257,7 @@ func runProfileShow(c *Ctx, args []string) int { | |||
| 257 | if len(args) == 1 { | 257 | if len(args) == 1 { |
| 258 | name = args[0] | 258 | name = args[0] |
| 259 | } else if len(args) > 1 { | 259 | } else if len(args) > 1 { |
| 260 | return c.fail(protocol.ExitUsage, "usage: profile show [name]") | 260 | return c.usage() |
| 261 | } | 261 | } |
| 262 | kind, id := "", int64(0) | 262 | kind, id := "", int64(0) |
| 263 | if u, err := c.Store.UserByUsername(name); err == nil { | 263 | if u, err := c.Store.UserByUsername(name); err == nil { |
| @@ -360,8 +360,7 @@ func runProfileSet(c *Ctx, args []string) int { | |||
| 360 | return c.failInput(err) | 360 | return c.failInput(err) |
| 361 | } | 361 | } |
| 362 | if len(rest) != 0 { | 362 | if len(rest) != 0 { |
| 363 | return c.fail(protocol.ExitUsage, | 363 | return c.usage() |
| 364 | "usage: profile set [--description <d>] [--website <url>] [--about <text>|--file -] [--about-format md|org] [--link <label|url>]...") | ||
| 365 | } | 364 | } |
| 366 | if e.empty() { | 365 | if e.empty() { |
| 367 | return c.fail(protocol.ExitUsage, "nothing to set: pass --description, --website, --about and/or --link") | 366 | return c.fail(protocol.ExitUsage, "nothing to set: pass --description, --website, --about and/or --link") |
| @@ -388,8 +387,7 @@ func runOrgProfile(c *Ctx, args []string) int { | |||
| 388 | return c.failInput(err) | 387 | return c.failInput(err) |
| 389 | } | 388 | } |
| 390 | if len(rest) != 1 { | 389 | if len(rest) != 1 { |
| 391 | return c.fail(protocol.ExitUsage, | 390 | return c.usage() |
| 392 | "usage: org profile <org> [--description <d>] [--website <url>] [--about <text>|--file -] [--about-format md|org] [--link <label|url>]...") | ||
| 393 | } | 391 | } |
| 394 | name := rest[0] | 392 | name := rest[0] |
| 395 | if e.empty() { | 393 | if e.empty() { |
internal/control/quota.go +2 −2
| @@ -93,7 +93,7 @@ func runAdminUserLimits(c *Ctx, args []string) int { | |||
| 93 | return code | 93 | return code |
| 94 | } | 94 | } |
| 95 | if len(args) < 1 { | 95 | if len(args) < 1 { |
| 96 | return c.fail(protocol.ExitUsage, "usage: admin user limits <username> [--repos <n>|default] [--bytes <n>|default]") | 96 | return c.usage() |
| 97 | } | 97 | } |
| 98 | u, err := c.Store.UserByUsername(args[0]) | 98 | u, err := c.Store.UserByUsername(args[0]) |
| 99 | if err != nil { | 99 | if err != nil { |
| @@ -116,7 +116,7 @@ func runAdminUserLimits(c *Ctx, args []string) int { | |||
| 116 | case "--bytes": | 116 | case "--bytes": |
| 117 | target = &l.Bytes | 117 | target = &l.Bytes |
| 118 | default: | 118 | default: |
| 119 | return c.fail(protocol.ExitUsage, "usage: admin user limits <username> [--repos <n>|default] [--bytes <n>|default]") | 119 | return c.usage() |
| 120 | } | 120 | } |
| 121 | if v == "default" { | 121 | if v == "default" { |
| 122 | *target = nil | 122 | *target = nil |
internal/control/read.go +8 −11
| @@ -48,7 +48,7 @@ func init() { | |||
| 48 | 48 | ||
| 49 | func runRepoRefs(c *Ctx, args []string) int { | 49 | func runRepoRefs(c *Ctx, args []string) int { |
| 50 | if len(args) != 1 { | 50 | if len(args) != 1 { |
| 51 | return c.fail(protocol.ExitUsage, "usage: repo refs <owner/name>") | 51 | return c.usage() |
| 52 | } | 52 | } |
| 53 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 53 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 54 | if code >= 0 { | 54 | if code >= 0 { |
| @@ -93,8 +93,7 @@ func runRepoRefs(c *Ctx, args []string) int { | |||
| 93 | const BlameSpan = 1000 | 93 | const BlameSpan = 1000 |
| 94 | 94 | ||
| 95 | func runRepoBlame(c *Ctx, args []string) int { | 95 | func runRepoBlame(c *Ctx, args []string) int { |
| 96 | const usage = "repo blame <owner/name> <path> [--ref <ref>] [--from <n>] [--to <n>]" | 96 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref", "--from", "--to"}, MaxPos: -1, Usage: c.Cmd.Usage}) |
| 97 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref", "--from", "--to"}, MaxPos: -1, Usage: usage}) | ||
| 98 | if err != nil { | 97 | if err != nil { |
| 99 | return c.fail(protocol.ExitUsage, "%v", err) | 98 | return c.fail(protocol.ExitUsage, "%v", err) |
| 100 | } | 99 | } |
| @@ -111,7 +110,7 @@ func runRepoBlame(c *Ctx, args []string) int { | |||
| 111 | *dst = n | 110 | *dst = n |
| 112 | } | 111 | } |
| 113 | if len(rest) != 2 { | 112 | if len(rest) != 2 { |
| 114 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 113 | return c.usage() |
| 115 | } | 114 | } |
| 116 | repo, code := resolveRepo(c, rest[0], policy.CanRead) | 115 | repo, code := resolveRepo(c, rest[0], policy.CanRead) |
| 117 | if code >= 0 { | 116 | if code >= 0 { |
| @@ -204,7 +203,7 @@ func runRepoBlame(c *Ctx, args []string) int { | |||
| 204 | // off argv. Positionals are returned in order so each command can name them | 203 | // off argv. Positionals are returned in order so each command can name them |
| 205 | // in its own usage message. | 204 | // in its own usage message. |
| 206 | func readArgs(c *Ctx, args []string, usage string, maxPos int) (pos []string, ref string, code int) { | 205 | func readArgs(c *Ctx, args []string, usage string, maxPos int) (pos []string, ref string, code int) { |
| 207 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref"}, MaxPos: maxPos, Usage: usage}) | 206 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref"}, MaxPos: maxPos, Usage: c.Cmd.Usage}) |
| 208 | if err != nil { | 207 | if err != nil { |
| 209 | return nil, "", c.fail(protocol.ExitUsage, "%v", err) | 208 | return nil, "", c.fail(protocol.ExitUsage, "%v", err) |
| 210 | } | 209 | } |
| @@ -239,13 +238,12 @@ type entryOut struct { | |||
| 239 | } | 238 | } |
| 240 | 239 | ||
| 241 | func runRepoTree(c *Ctx, args []string) int { | 240 | func runRepoTree(c *Ctx, args []string) int { |
| 242 | const usage = "repo tree <owner/name> [<path>] [--ref <ref>]" | 241 | pos, ref, code := readArgs(c, args, c.Cmd.Usage, 2) |
| 243 | pos, ref, code := readArgs(c, args, usage, 2) | ||
| 244 | if code >= 0 { | 242 | if code >= 0 { |
| 245 | return code | 243 | return code |
| 246 | } | 244 | } |
| 247 | if len(pos) == 0 { | 245 | if len(pos) == 0 { |
| 248 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 246 | return c.usage() |
| 249 | } | 247 | } |
| 250 | repo, code := resolveRepo(c, pos[0], policy.CanRead) | 248 | repo, code := resolveRepo(c, pos[0], policy.CanRead) |
| 251 | if code >= 0 { | 249 | if code >= 0 { |
| @@ -303,13 +301,12 @@ func sizeCol(e entryOut) string { | |||
| 303 | } | 301 | } |
| 304 | 302 | ||
| 305 | func runRepoCat(c *Ctx, args []string) int { | 303 | func runRepoCat(c *Ctx, args []string) int { |
| 306 | const usage = "repo cat <owner/name> <path> [--ref <ref>]" | 304 | pos, ref, code := readArgs(c, args, c.Cmd.Usage, 2) |
| 307 | pos, ref, code := readArgs(c, args, usage, 2) | ||
| 308 | if code >= 0 { | 305 | if code >= 0 { |
| 309 | return code | 306 | return code |
| 310 | } | 307 | } |
| 311 | if len(pos) != 2 { | 308 | if len(pos) != 2 { |
| 312 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 309 | return c.usage() |
| 313 | } | 310 | } |
| 314 | repo, code := resolveRepo(c, pos[0], policy.CanRead) | 311 | repo, code := resolveRepo(c, pos[0], policy.CanRead) |
| 315 | if code >= 0 { | 312 | if code >= 0 { |
internal/control/register.go +5 −5
| @@ -45,7 +45,7 @@ func init() { | |||
| 45 | 45 | ||
| 46 | func runEmailList(c *Ctx, args []string) int { | 46 | func runEmailList(c *Ctx, args []string) int { |
| 47 | if len(args) != 0 { | 47 | if len(args) != 0 { |
| 48 | return c.fail(protocol.ExitUsage, "usage: email list [--json]") | 48 | return c.usage() |
| 49 | } | 49 | } |
| 50 | emails, err := c.Store.ListEmails(c.User.ID) | 50 | emails, err := c.Store.ListEmails(c.User.ID) |
| 51 | if err != nil { | 51 | if err != nil { |
| @@ -89,7 +89,7 @@ func emailErr(c *Ctx, verb string, err error) int { | |||
| 89 | 89 | ||
| 90 | func runEmailRemove(c *Ctx, args []string) int { | 90 | func runEmailRemove(c *Ctx, args []string) int { |
| 91 | if len(args) != 1 { | 91 | if len(args) != 1 { |
| 92 | return c.fail(protocol.ExitUsage, "usage: email remove <address>") | 92 | return c.usage() |
| 93 | } | 93 | } |
| 94 | if err := c.Store.RemoveEmail(c.User.ID, args[0]); err != nil { | 94 | if err := c.Store.RemoveEmail(c.User.ID, args[0]); err != nil { |
| 95 | return emailErr(c, "removing address", err) | 95 | return emailErr(c, "removing address", err) |
| @@ -101,7 +101,7 @@ func runEmailRemove(c *Ctx, args []string) int { | |||
| 101 | 101 | ||
| 102 | func runEmailPrimary(c *Ctx, args []string) int { | 102 | func runEmailPrimary(c *Ctx, args []string) int { |
| 103 | if len(args) != 1 { | 103 | if len(args) != 1 { |
| 104 | return c.fail(protocol.ExitUsage, "usage: email primary <address>") | 104 | return c.usage() |
| 105 | } | 105 | } |
| 106 | if err := c.Store.SetPrimaryEmail(c.User.ID, args[0]); err != nil { | 106 | if err := c.Store.SetPrimaryEmail(c.User.ID, args[0]); err != nil { |
| 107 | return emailErr(c, "setting primary", err) | 107 | return emailErr(c, "setting primary", err) |
| @@ -136,7 +136,7 @@ const maxEmailAddsPerHour = 5 | |||
| 136 | 136 | ||
| 137 | func runEmailAdd(c *Ctx, args []string) int { | 137 | func runEmailAdd(c *Ctx, args []string) int { |
| 138 | if len(args) != 1 || !strings.Contains(args[0], "@") { | 138 | if len(args) != 1 || !strings.Contains(args[0], "@") { |
| 139 | return c.fail(protocol.ExitUsage, "usage: email add <address>") | 139 | return c.usage() |
| 140 | } | 140 | } |
| 141 | if c.Cfg.Mail.SMTPHost == "" { | 141 | if c.Cfg.Mail.SMTPHost == "" { |
| 142 | return c.fail(protocol.ExitFailure, "this instance has no SMTP configured; ask an admin to verify the address (gitbayd admin email verify)") | 142 | return c.fail(protocol.ExitFailure, "this instance has no SMTP configured; ask an admin to verify the address (gitbayd admin email verify)") |
| @@ -161,7 +161,7 @@ func runEmailAdd(c *Ctx, args []string) int { | |||
| 161 | 161 | ||
| 162 | func runEmailVerify(c *Ctx, args []string) int { | 162 | func runEmailVerify(c *Ctx, args []string) int { |
| 163 | if len(args) != 1 { | 163 | if len(args) != 1 { |
| 164 | return c.fail(protocol.ExitUsage, "usage: email verify <code>") | 164 | return c.usage() |
| 165 | } | 165 | } |
| 166 | hash := store.HashToken(args[0]) | 166 | hash := store.HashToken(args[0]) |
| 167 | address, err := c.Store.ConsumeEmailToken(c.User.ID, hash) | 167 | address, err := c.Store.ConsumeEmailToken(c.User.ID, hash) |
internal/control/release.go +8 −10
| @@ -76,15 +76,14 @@ func releaseRef(c *Ctx, args []string, perm func(store.User, store.Repo, string) | |||
| 76 | } | 76 | } |
| 77 | 77 | ||
| 78 | func runReleaseCreate(c *Ctx, args []string) int { | 78 | func runReleaseCreate(c *Ctx, args []string) int { |
| 79 | const usage = "usage: release create <owner/name> <tag> [--title <t>] [--notes <n> | --file -] [--format md|org]" | 79 | f, err := parseFlags(args, flagSpec{Values: []string{"--title", "--notes", "--file", "--format"}, MaxPos: 2, Usage: c.Cmd.Usage}) |
| 80 | f, err := parseFlags(args, flagSpec{Values: []string{"--title", "--notes", "--file", "--format"}, MaxPos: 2, Usage: usage}) | ||
| 81 | if err != nil { | 80 | if err != nil { |
| 82 | return c.fail(protocol.ExitUsage, "%v", err) | 81 | return c.fail(protocol.ExitUsage, "%v", err) |
| 83 | } | 82 | } |
| 84 | path, tag := f.pos(0), f.pos(1) | 83 | path, tag := f.pos(0), f.pos(1) |
| 85 | title, notes, file, format := f.Value("--title"), f.Value("--notes"), f.Value("--file"), f.Value("--format") | 84 | title, notes, file, format := f.Value("--title"), f.Value("--notes"), f.Value("--file"), f.Value("--format") |
| 86 | if path == "" || tag == "" { | 85 | if path == "" || tag == "" { |
| 87 | return c.fail(protocol.ExitUsage, usage) | 86 | return c.usage() |
| 88 | } | 87 | } |
| 89 | fmtName, err := markupFormat(format) | 88 | fmtName, err := markupFormat(format) |
| 90 | if err != nil { | 89 | if err != nil { |
| @@ -149,8 +148,7 @@ func releaseToOut(r store.Release, withNotes bool) releaseOut { | |||
| 149 | } | 148 | } |
| 150 | 149 | ||
| 151 | func runReleaseEdit(c *Ctx, args []string) int { | 150 | func runReleaseEdit(c *Ctx, args []string) int { |
| 152 | const usage = "usage: release edit <owner/name> <tag> [--title <t>] [--notes <n> | --file -] [--format md|org]" | 151 | f, err := parseFlags(args, flagSpec{Values: []string{"--title", "--notes", "--file", "--format"}, MaxPos: 2, Usage: c.Cmd.Usage}) |
| 153 | f, err := parseFlags(args, flagSpec{Values: []string{"--title", "--notes", "--file", "--format"}, MaxPos: 2, Usage: usage}) | ||
| 154 | if err != nil { | 152 | if err != nil { |
| 155 | return c.fail(protocol.ExitUsage, "%v", err) | 153 | return c.fail(protocol.ExitUsage, "%v", err) |
| 156 | } | 154 | } |
| @@ -162,7 +160,7 @@ func runReleaseEdit(c *Ctx, args []string) int { | |||
| 162 | return c.failInput(err) | 160 | return c.failInput(err) |
| 163 | } | 161 | } |
| 164 | if path == "" || tag == "" || (!setTitle && !setNotes && fmtName == "") { | 162 | if path == "" || tag == "" || (!setTitle && !setNotes && fmtName == "") { |
| 165 | return c.fail(protocol.ExitUsage, usage) | 163 | return c.usage() |
| 166 | } | 164 | } |
| 167 | repo, code := resolveRepo(c, path, policy.CanWrite) | 165 | repo, code := resolveRepo(c, path, policy.CanWrite) |
| 168 | if code >= 0 { | 166 | if code >= 0 { |
| @@ -200,7 +198,7 @@ func runReleaseEdit(c *Ctx, args []string) int { | |||
| 200 | 198 | ||
| 201 | func runReleaseList(c *Ctx, args []string) int { | 199 | func runReleaseList(c *Ctx, args []string) int { |
| 202 | if len(args) != 1 { | 200 | if len(args) != 1 { |
| 203 | return c.fail(protocol.ExitUsage, "usage: release list <owner/name>") | 201 | return c.usage() |
| 204 | } | 202 | } |
| 205 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 203 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 206 | if code >= 0 { | 204 | if code >= 0 { |
| @@ -267,7 +265,7 @@ func runReleaseDelete(c *Ctx, args []string) int { | |||
| 267 | 265 | ||
| 268 | func runAssetAdd(c *Ctx, args []string) int { | 266 | func runAssetAdd(c *Ctx, args []string) int { |
| 269 | if len(args) != 3 { | 267 | if len(args) != 3 { |
| 270 | return c.fail(protocol.ExitUsage, "usage: release asset add <owner/name> <tag> <filename> < file") | 268 | return c.usage() |
| 271 | } | 269 | } |
| 272 | repo, rel, code := releaseRef(c, args[:2], policy.CanWrite) | 270 | repo, rel, code := releaseRef(c, args[:2], policy.CanWrite) |
| 273 | if code >= 0 { | 271 | if code >= 0 { |
| @@ -319,7 +317,7 @@ func runAssetAdd(c *Ctx, args []string) int { | |||
| 319 | 317 | ||
| 320 | func runAssetGet(c *Ctx, args []string) int { | 318 | func runAssetGet(c *Ctx, args []string) int { |
| 321 | if len(args) != 3 { | 319 | if len(args) != 3 { |
| 322 | return c.fail(protocol.ExitUsage, "usage: release asset get <owner/name> <tag> <filename> > file") | 320 | return c.usage() |
| 323 | } | 321 | } |
| 324 | repo, rel, code := releaseRef(c, args[:2], policy.CanRead) | 322 | repo, rel, code := releaseRef(c, args[:2], policy.CanRead) |
| 325 | if code >= 0 { | 323 | if code >= 0 { |
| @@ -342,7 +340,7 @@ func runAssetGet(c *Ctx, args []string) int { | |||
| 342 | 340 | ||
| 343 | func runAssetRemove(c *Ctx, args []string) int { | 341 | func runAssetRemove(c *Ctx, args []string) int { |
| 344 | if len(args) != 3 { | 342 | if len(args) != 3 { |
| 345 | return c.fail(protocol.ExitUsage, "usage: release asset remove <owner/name> <tag> <filename>") | 343 | return c.usage() |
| 346 | } | 344 | } |
| 347 | repo, rel, code := releaseRef(c, args[:2], policy.CanWrite) | 345 | repo, rel, code := releaseRef(c, args[:2], policy.CanWrite) |
| 348 | if code >= 0 { | 346 | if code >= 0 { |
internal/control/repo.go +32 −38
| @@ -141,7 +141,7 @@ func validQuery(q string) error { | |||
| 141 | // stay available so an archived repo can be managed and unarchived. | 141 | // stay available so an archived repo can be managed and unarchived. |
| 142 | func refuseArchived(c *Ctx, repo store.Repo) int { | 142 | func refuseArchived(c *Ctx, repo store.Repo) int { |
| 143 | if repo.Settings.Archived { | 143 | if repo.Settings.Archived { |
| 144 | return c.fail(protocol.ExitDenied, "%s is archived and read-only", repo.Path()) | 144 | return c.fail(protocol.ExitDenied, "%s is archived and read-only; unarchive it first", repo.Path()) |
| 145 | } | 145 | } |
| 146 | return -1 | 146 | return -1 |
| 147 | } | 147 | } |
| @@ -165,7 +165,7 @@ func resolveRepo(c *Ctx, path string, check func(store.User, store.Repo, string) | |||
| 165 | // Invisible repos 404, per the enumeration rule. | 165 | // Invisible repos 404, per the enumeration rule. |
| 166 | return repo, c.fail(protocol.ExitNotFound, "repository %s not found", path) | 166 | return repo, c.fail(protocol.ExitNotFound, "repository %s not found", path) |
| 167 | } | 167 | } |
| 168 | return repo, c.fail(protocol.ExitDenied, "permission denied on %s", path) | 168 | return repo, c.fail(protocol.ExitDenied, "permission denied on %s; ask its owner for access", path) |
| 169 | } | 169 | } |
| 170 | return repo, -1 | 170 | return repo, -1 |
| 171 | } | 171 | } |
| @@ -181,7 +181,7 @@ func runRepoCreate(c *Ctx, args []string) int { | |||
| 181 | } | 181 | } |
| 182 | owner, name, ok := strings.Cut(path, "/") | 182 | owner, name, ok := strings.Cut(path, "/") |
| 183 | if !ok { | 183 | if !ok { |
| 184 | return c.fail(protocol.ExitUsage, "usage: repo create <owner/name> [--private]") | 184 | return c.usage() |
| 185 | } | 185 | } |
| 186 | if err := policyValidateRepoName(name); err != nil { | 186 | if err := policyValidateRepoName(name); err != nil { |
| 187 | return c.failInput(err) | 187 | return c.failInput(err) |
| @@ -247,7 +247,7 @@ func runRepoList(c *Ctx, args []string) int { | |||
| 247 | return code | 247 | return code |
| 248 | } | 248 | } |
| 249 | if len(args) != 0 { | 249 | if len(args) != 0 { |
| 250 | return c.fail(protocol.ExitUsage, "usage: repo list [--limit <n>] [--cursor <c>]") | 250 | return c.usage() |
| 251 | } | 251 | } |
| 252 | repos, err := c.Store.ListReposForUser(c.User.ID, p.queryLimit(), p.key) | 252 | repos, err := c.Store.ListReposForUser(c.User.ID, p.queryLimit(), p.key) |
| 253 | if err != nil { | 253 | if err != nil { |
| @@ -278,7 +278,7 @@ func runRepoList(c *Ctx, args []string) int { | |||
| 278 | 278 | ||
| 279 | func runRepoShow(c *Ctx, args []string) int { | 279 | func runRepoShow(c *Ctx, args []string) int { |
| 280 | if len(args) != 1 { | 280 | if len(args) != 1 { |
| 281 | return c.fail(protocol.ExitUsage, "usage: repo show <owner/name>") | 281 | return c.usage() |
| 282 | } | 282 | } |
| 283 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 283 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 284 | if code >= 0 { | 284 | if code >= 0 { |
| @@ -393,7 +393,7 @@ func runRepoShow(c *Ctx, args []string) int { | |||
| 393 | 393 | ||
| 394 | func runRepoTransfer(c *Ctx, args []string) int { | 394 | func runRepoTransfer(c *Ctx, args []string) int { |
| 395 | if len(args) != 2 { | 395 | if len(args) != 2 { |
| 396 | return c.fail(protocol.ExitUsage, "usage: repo transfer <owner/name> <new-owner>") | 396 | return c.usage() |
| 397 | } | 397 | } |
| 398 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 398 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 399 | if code >= 0 { | 399 | if code >= 0 { |
| @@ -452,7 +452,7 @@ func runRepoTransfer(c *Ctx, args []string) int { | |||
| 452 | 452 | ||
| 453 | func runRepoRename(c *Ctx, args []string) int { | 453 | func runRepoRename(c *Ctx, args []string) int { |
| 454 | if len(args) != 2 { | 454 | if len(args) != 2 { |
| 455 | return c.fail(protocol.ExitUsage, "usage: repo rename <owner/name> <new-name>") | 455 | return c.usage() |
| 456 | } | 456 | } |
| 457 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 457 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 458 | if code >= 0 { | 458 | if code >= 0 { |
| @@ -496,11 +496,11 @@ func runRepoDelete(c *Ctx, args []string) int { | |||
| 496 | } else if path == "" { | 496 | } else if path == "" { |
| 497 | path = a | 497 | path = a |
| 498 | } else { | 498 | } else { |
| 499 | return c.fail(protocol.ExitUsage, "usage: repo delete <owner/name> --yes") | 499 | return c.usage() |
| 500 | } | 500 | } |
| 501 | } | 501 | } |
| 502 | if path == "" { | 502 | if path == "" { |
| 503 | return c.fail(protocol.ExitUsage, "usage: repo delete <owner/name> --yes") | 503 | return c.usage() |
| 504 | } | 504 | } |
| 505 | repo, code := resolveRepo(c, path, policy.CanAdmin) | 505 | repo, code := resolveRepo(c, path, policy.CanAdmin) |
| 506 | if code >= 0 { | 506 | if code >= 0 { |
| @@ -540,7 +540,7 @@ func deleteRepo(c *Ctx, repo store.Repo) int { | |||
| 540 | 540 | ||
| 541 | func runAccessGrant(c *Ctx, args []string) int { | 541 | func runAccessGrant(c *Ctx, args []string) int { |
| 542 | if len(args) != 3 || !slices.Contains([]string{"read", "write", "admin"}, args[2]) { | 542 | if len(args) != 3 || !slices.Contains([]string{"read", "write", "admin"}, args[2]) { |
| 543 | return c.fail(protocol.ExitUsage, "usage: repo access grant <owner/name> <user> read|write|admin") | 543 | return c.usage() |
| 544 | } | 544 | } |
| 545 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 545 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 546 | if code >= 0 { | 546 | if code >= 0 { |
| @@ -559,7 +559,7 @@ func runAccessGrant(c *Ctx, args []string) int { | |||
| 559 | 559 | ||
| 560 | func runAccessRevoke(c *Ctx, args []string) int { | 560 | func runAccessRevoke(c *Ctx, args []string) int { |
| 561 | if len(args) != 2 { | 561 | if len(args) != 2 { |
| 562 | return c.fail(protocol.ExitUsage, "usage: repo access revoke <owner/name> <user>") | 562 | return c.usage() |
| 563 | } | 563 | } |
| 564 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 564 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 565 | if code >= 0 { | 565 | if code >= 0 { |
| @@ -581,7 +581,7 @@ func runAccessRevoke(c *Ctx, args []string) int { | |||
| 581 | 581 | ||
| 582 | func runAccessList(c *Ctx, args []string) int { | 582 | func runAccessList(c *Ctx, args []string) int { |
| 583 | if len(args) != 1 { | 583 | if len(args) != 1 { |
| 584 | return c.fail(protocol.ExitUsage, "usage: repo access list <owner/name>") | 584 | return c.usage() |
| 585 | } | 585 | } |
| 586 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 586 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 587 | if code >= 0 { | 587 | if code >= 0 { |
| @@ -609,7 +609,7 @@ func runAccessList(c *Ctx, args []string) int { | |||
| 609 | 609 | ||
| 610 | func runSettingsShow(c *Ctx, args []string) int { | 610 | func runSettingsShow(c *Ctx, args []string) int { |
| 611 | if len(args) != 1 { | 611 | if len(args) != 1 { |
| 612 | return c.fail(protocol.ExitUsage, "usage: repo settings show <owner/name>") | 612 | return c.usage() |
| 613 | } | 613 | } |
| 614 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 614 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 615 | if code >= 0 { | 615 | if code >= 0 { |
| @@ -623,7 +623,7 @@ func runSettingsShow(c *Ctx, args []string) int { | |||
| 623 | 623 | ||
| 624 | func runSetDescription(c *Ctx, args []string) int { | 624 | func runSetDescription(c *Ctx, args []string) int { |
| 625 | if len(args) != 2 { | 625 | if len(args) != 2 { |
| 626 | return c.fail(protocol.ExitUsage, "usage: repo settings description <owner/name> <text>") | 626 | return c.usage() |
| 627 | } | 627 | } |
| 628 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 628 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 629 | if code >= 0 { | 629 | if code >= 0 { |
| @@ -640,7 +640,7 @@ func runSetDescription(c *Ctx, args []string) int { | |||
| 640 | 640 | ||
| 641 | func runSetDefaultBranch(c *Ctx, args []string) int { | 641 | func runSetDefaultBranch(c *Ctx, args []string) int { |
| 642 | if len(args) != 2 { | 642 | if len(args) != 2 { |
| 643 | return c.fail(protocol.ExitUsage, "usage: repo settings default-branch <owner/name> <branch>") | 643 | return c.usage() |
| 644 | } | 644 | } |
| 645 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 645 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 646 | if code >= 0 { | 646 | if code >= 0 { |
| @@ -664,7 +664,7 @@ func runSetDefaultBranch(c *Ctx, args []string) int { | |||
| 664 | 664 | ||
| 665 | func runSetWebsite(c *Ctx, args []string) int { | 665 | func runSetWebsite(c *Ctx, args []string) int { |
| 666 | if len(args) != 2 { | 666 | if len(args) != 2 { |
| 667 | return c.fail(protocol.ExitUsage, "usage: repo settings website <owner/name> <url>") | 667 | return c.usage() |
| 668 | } | 668 | } |
| 669 | site := strings.TrimSpace(args[1]) | 669 | site := strings.TrimSpace(args[1]) |
| 670 | if err := validateWebsite(site); err != nil { | 670 | if err := validateWebsite(site); err != nil { |
| @@ -691,7 +691,7 @@ func runSetWebsite(c *Ctx, args []string) int { | |||
| 691 | 691 | ||
| 692 | func runSetVisibility(c *Ctx, args []string) int { | 692 | func runSetVisibility(c *Ctx, args []string) int { |
| 693 | if len(args) != 2 || (args[1] != "public" && args[1] != "private") { | 693 | if len(args) != 2 || (args[1] != "public" && args[1] != "private") { |
| 694 | return c.fail(protocol.ExitUsage, "usage: repo settings visibility <owner/name> public|private") | 694 | return c.usage() |
| 695 | } | 695 | } |
| 696 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 696 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 697 | if code >= 0 { | 697 | if code >= 0 { |
| @@ -724,7 +724,7 @@ func setRepoVisibility(c *Ctx, repo store.Repo, visibility string) int { | |||
| 724 | 724 | ||
| 725 | func runGitDaemon(c *Ctx, args []string) int { | 725 | func runGitDaemon(c *Ctx, args []string) int { |
| 726 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { | 726 | if len(args) != 2 || (args[1] != "on" && args[1] != "off") { |
| 727 | return c.fail(protocol.ExitUsage, "usage: repo settings git-daemon <owner/name> on|off") | 727 | return c.usage() |
| 728 | } | 728 | } |
| 729 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 729 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 730 | if code >= 0 { | 730 | if code >= 0 { |
| @@ -748,12 +748,8 @@ func runArchive(c *Ctx, args []string) int { return setArchived(c, args, true) | |||
| 748 | func runUnarchive(c *Ctx, args []string) int { return setArchived(c, args, false) } | 748 | func runUnarchive(c *Ctx, args []string) int { return setArchived(c, args, false) } |
| 749 | 749 | ||
| 750 | func setArchived(c *Ctx, args []string, archived bool) int { | 750 | func setArchived(c *Ctx, args []string, archived bool) int { |
| 751 | verb := "archive" | ||
| 752 | if !archived { | ||
| 753 | verb = "unarchive" | ||
| 754 | } | ||
| 755 | if len(args) != 1 { | 751 | if len(args) != 1 { |
| 756 | return c.fail(protocol.ExitUsage, "usage: repo %s <owner/name>", verb) | 752 | return c.usage() |
| 757 | } | 753 | } |
| 758 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 754 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 759 | if code >= 0 { | 755 | if code >= 0 { |
| @@ -782,7 +778,7 @@ func archiveRepo(c *Ctx, repo store.Repo, archived bool) int { | |||
| 782 | 778 | ||
| 783 | func runTopicsList(c *Ctx, args []string) int { | 779 | func runTopicsList(c *Ctx, args []string) int { |
| 784 | if len(args) != 1 { | 780 | if len(args) != 1 { |
| 785 | return c.fail(protocol.ExitUsage, "usage: repo topics <owner/name>") | 781 | return c.usage() |
| 786 | } | 782 | } |
| 787 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 783 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 788 | if code >= 0 { | 784 | if code >= 0 { |
| @@ -803,12 +799,8 @@ func runTopicsAdd(c *Ctx, args []string) int { return editTopics(c, args, tru | |||
| 803 | func runTopicsRemove(c *Ctx, args []string) int { return editTopics(c, args, false) } | 799 | func runTopicsRemove(c *Ctx, args []string) int { return editTopics(c, args, false) } |
| 804 | 800 | ||
| 805 | func editTopics(c *Ctx, args []string, add bool) int { | 801 | func editTopics(c *Ctx, args []string, add bool) int { |
| 806 | verb := "add" | ||
| 807 | if !add { | ||
| 808 | verb = "remove" | ||
| 809 | } | ||
| 810 | if len(args) < 2 { | 802 | if len(args) < 2 { |
| 811 | return c.fail(protocol.ExitUsage, "usage: repo topics %s <owner/name> <topic>...", verb) | 803 | return c.usage() |
| 812 | } | 804 | } |
| 813 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 805 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 814 | if code >= 0 { | 806 | if code >= 0 { |
| @@ -854,7 +846,9 @@ func editTopics(c *Ctx, args []string, add bool) int { | |||
| 854 | return c.fail(protocol.ExitFailure, "%v", err) | 846 | return c.fail(protocol.ExitFailure, "%v", err) |
| 855 | } | 847 | } |
| 856 | return c.emit(now, func(w io.Writer) { | 848 | return c.emit(now, func(w io.Writer) { |
| 857 | fmt.Fprintf(w, "topics on %s: %s\n", repo.Path(), strings.Join(now, ", ")) | 849 | for _, t := range now { |
| 850 | fmt.Fprintln(w, t) | ||
| 851 | } | ||
| 858 | }) | 852 | }) |
| 859 | } | 853 | } |
| 860 | 854 | ||
| @@ -862,7 +856,7 @@ func editTopics(c *Ctx, args []string, add bool) int { | |||
| 862 | // and topics of every repository the caller can see. | 856 | // and topics of every repository the caller can see. |
| 863 | func runRepoSearch(c *Ctx, args []string) int { | 857 | func runRepoSearch(c *Ctx, args []string) int { |
| 864 | if len(args) != 1 { | 858 | if len(args) != 1 { |
| 865 | return c.fail(protocol.ExitUsage, "usage: repo search <query>") | 859 | return c.usage() |
| 866 | } | 860 | } |
| 867 | if err := validQuery(args[0]); err != nil { | 861 | if err := validQuery(args[0]); err != nil { |
| 868 | return c.failInput(err) | 862 | return c.failInput(err) |
| @@ -929,7 +923,7 @@ func runRepoGrep(c *Ctx, args []string) int { | |||
| 929 | } | 923 | } |
| 930 | path, query, ref := f.pos(0), f.pos(1), f.Value("--ref") | 924 | path, query, ref := f.pos(0), f.pos(1), f.Value("--ref") |
| 931 | if path == "" || query == "" { | 925 | if path == "" || query == "" { |
| 932 | return c.fail(protocol.ExitUsage, "usage: repo grep <owner/name> <query> [--ref <ref>]") | 926 | return c.usage() |
| 933 | } | 927 | } |
| 934 | if err := validQuery(query); err != nil { | 928 | if err := validQuery(query); err != nil { |
| 935 | return c.failInput(err) | 929 | return c.failInput(err) |
| @@ -974,7 +968,7 @@ func setPinned(c *Ctx, args []string, pin bool) int { | |||
| 974 | verb = "unpin" | 968 | verb = "unpin" |
| 975 | } | 969 | } |
| 976 | if len(args) != 1 { | 970 | if len(args) != 1 { |
| 977 | return c.fail(protocol.ExitUsage, "usage: repo %s <owner/name>", verb) | 971 | return c.usage() |
| 978 | } | 972 | } |
| 979 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 973 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 980 | if code >= 0 { | 974 | if code >= 0 { |
| @@ -1008,7 +1002,7 @@ func setBookmarked(c *Ctx, args []string, on bool) int { | |||
| 1008 | verb = "unbookmark" | 1002 | verb = "unbookmark" |
| 1009 | } | 1003 | } |
| 1010 | if len(args) != 1 { | 1004 | if len(args) != 1 { |
| 1011 | return c.fail(protocol.ExitUsage, "usage: repo %s <owner/name>", verb) | 1005 | return c.usage() |
| 1012 | } | 1006 | } |
| 1013 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 1007 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 1014 | if code >= 0 { | 1008 | if code >= 0 { |
| @@ -1040,7 +1034,7 @@ type BookmarkOut struct { | |||
| 1040 | 1034 | ||
| 1041 | func runRepoBookmarks(c *Ctx, args []string) int { | 1035 | func runRepoBookmarks(c *Ctx, args []string) int { |
| 1042 | if len(args) != 0 { | 1036 | if len(args) != 0 { |
| 1043 | return c.fail(protocol.ExitUsage, "usage: repo bookmarks") | 1037 | return c.usage() |
| 1044 | } | 1038 | } |
| 1045 | repos, err := c.Store.ListBookmarks(c.User.ID) | 1039 | repos, err := c.Store.ListBookmarks(c.User.ID) |
| 1046 | if err != nil { | 1040 | if err != nil { |
| @@ -1077,7 +1071,7 @@ func runUnprotectTag(c *Ctx, args []string) int { return setProtectTag(c, args, | |||
| 1077 | 1071 | ||
| 1078 | func setProtectTag(c *Ctx, args []string, protect bool) int { | 1072 | func setProtectTag(c *Ctx, args []string, protect bool) int { |
| 1079 | if len(args) != 2 { | 1073 | if len(args) != 2 { |
| 1080 | return c.fail(protocol.ExitUsage, "usage: repo settings protect-tag|unprotect-tag <owner/name> <glob>") | 1074 | return c.usage() |
| 1081 | } | 1075 | } |
| 1082 | glob := args[1] | 1076 | glob := args[1] |
| 1083 | if _, err := path.Match(glob, "x"); err != nil || glob == "" { | 1077 | if _, err := path.Match(glob, "x"); err != nil || glob == "" { |
| @@ -1114,7 +1108,7 @@ func runUnprotect(c *Ctx, args []string) int { return setProtect(c, args, false) | |||
| 1114 | 1108 | ||
| 1115 | func setProtect(c *Ctx, args []string, protect bool) int { | 1109 | func setProtect(c *Ctx, args []string, protect bool) int { |
| 1116 | if len(args) != 2 { | 1110 | if len(args) != 2 { |
| 1117 | return c.fail(protocol.ExitUsage, "usage: repo settings protect|unprotect <owner/name> <branch>") | 1111 | return c.usage() |
| 1118 | } | 1112 | } |
| 1119 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 1113 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 1120 | if code >= 0 { | 1114 | if code >= 0 { |
| @@ -1149,7 +1143,7 @@ func setProtect(c *Ctx, args []string, protect bool) int { | |||
| 1149 | func runRepoDiff(c *Ctx, args []string) int { | 1143 | func runRepoDiff(c *Ctx, args []string) int { |
| 1150 | f, err := parseFlags(args, flagSpec{MaxPos: 3, Usage: "repo diff <owner/name> <base> <head>"}) | 1144 | f, err := parseFlags(args, flagSpec{MaxPos: 3, Usage: "repo diff <owner/name> <base> <head>"}) |
| 1151 | if err != nil || len(f.Pos) != 3 { | 1145 | if err != nil || len(f.Pos) != 3 { |
| 1152 | return c.fail(protocol.ExitUsage, "usage: repo diff <owner/name> <base> <head>") | 1146 | return c.usage() |
| 1153 | } | 1147 | } |
| 1154 | repo, code := resolveRepo(c, f.pos(0), policy.CanRead) | 1148 | repo, code := resolveRepo(c, f.pos(0), policy.CanRead) |
| 1155 | if code >= 0 { | 1149 | if code >= 0 { |
internal/control/runnerrepo.go +3 −3
| @@ -33,7 +33,7 @@ func init() { | |||
| 33 | func runRepoRunnerAdd(c *Ctx, args []string) int { | 33 | func runRepoRunnerAdd(c *Ctx, args []string) int { |
| 34 | f, err := parseFlags(args, flagSpec{MaxPos: 1, Usage: "repo runner add <owner/name> < key.pub"}) | 34 | f, err := parseFlags(args, flagSpec{MaxPos: 1, Usage: "repo runner add <owner/name> < key.pub"}) |
| 35 | if err != nil || len(f.Pos) != 1 { | 35 | if err != nil || len(f.Pos) != 1 { |
| 36 | return c.fail(protocol.ExitUsage, "usage: repo runner add <owner/name> < key.pub") | 36 | return c.usage() |
| 37 | } | 37 | } |
| 38 | repo, code := resolveRepo(c, f.Pos[0], policy.CanAdmin) | 38 | repo, code := resolveRepo(c, f.Pos[0], policy.CanAdmin) |
| 39 | if code >= 0 { | 39 | if code >= 0 { |
| @@ -95,7 +95,7 @@ func runRepoRunnerAdd(c *Ctx, args []string) int { | |||
| 95 | 95 | ||
| 96 | func runRepoRunnerList(c *Ctx, args []string) int { | 96 | func runRepoRunnerList(c *Ctx, args []string) int { |
| 97 | if len(args) != 1 { | 97 | if len(args) != 1 { |
| 98 | return c.fail(protocol.ExitUsage, "usage: repo runner list <owner/name>") | 98 | return c.usage() |
| 99 | } | 99 | } |
| 100 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 100 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 101 | if code >= 0 { | 101 | if code >= 0 { |
| @@ -125,7 +125,7 @@ func runRepoRunnerList(c *Ctx, args []string) int { | |||
| 125 | 125 | ||
| 126 | func runRepoRunnerRemove(c *Ctx, args []string) int { | 126 | func runRepoRunnerRemove(c *Ctx, args []string) int { |
| 127 | if len(args) != 2 { | 127 | if len(args) != 2 { |
| 128 | return c.fail(protocol.ExitUsage, "usage: repo runner remove <owner/name> <fingerprint>") | 128 | return c.usage() |
| 129 | } | 129 | } |
| 130 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 130 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 131 | if code >= 0 { | 131 | if code >= 0 { |
internal/control/search.go +2 −3
| @@ -77,13 +77,12 @@ func Search(st *store.Store, root string, userID int64, q string, kinds []string | |||
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | func runSearch(c *Ctx, args []string) int { | 79 | func runSearch(c *Ctx, args []string) int { |
| 80 | const usage = "search <query> [--kind repo|issue|mr]" | 80 | f, err := parseFlags(args, flagSpec{Multi: []string{"--kind"}, MaxPos: 1, Usage: c.Cmd.Usage}) |
| 81 | f, err := parseFlags(args, flagSpec{Multi: []string{"--kind"}, MaxPos: 1, Usage: usage}) | ||
| 82 | if err != nil { | 81 | if err != nil { |
| 83 | return c.fail(protocol.ExitUsage, "%v", err) | 82 | return c.fail(protocol.ExitUsage, "%v", err) |
| 84 | } | 83 | } |
| 85 | if len(f.Pos) != 1 { | 84 | if len(f.Pos) != 1 { |
| 86 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 85 | return c.usage() |
| 87 | } | 86 | } |
| 88 | if err := validQuery(f.Pos[0]); err != nil { | 87 | if err := validQuery(f.Pos[0]); err != nil { |
| 89 | return c.failInput(err) | 88 | return c.failInput(err) |
internal/control/sig.go +4 −5
| @@ -37,7 +37,7 @@ func init() { | |||
| 37 | 37 | ||
| 38 | func runPGPAdd(c *Ctx, args []string) int { | 38 | func runPGPAdd(c *Ctx, args []string) int { |
| 39 | if len(args) != 0 { | 39 | if len(args) != 0 { |
| 40 | return c.fail(protocol.ExitUsage, "usage: pgp add < key.asc") | 40 | return c.usage() |
| 41 | } | 41 | } |
| 42 | raw, err := io.ReadAll(io.LimitReader(c.Stdin, 1<<20)) | 42 | raw, err := io.ReadAll(io.LimitReader(c.Stdin, 1<<20)) |
| 43 | if err != nil { | 43 | if err != nil { |
| @@ -88,7 +88,7 @@ func runPGPList(c *Ctx, args []string) int { | |||
| 88 | 88 | ||
| 89 | func runPGPRemove(c *Ctx, args []string) int { | 89 | func runPGPRemove(c *Ctx, args []string) int { |
| 90 | if len(args) != 1 { | 90 | if len(args) != 1 { |
| 91 | return c.fail(protocol.ExitUsage, "usage: pgp remove <fingerprint>") | 91 | return c.usage() |
| 92 | } | 92 | } |
| 93 | if err := c.Store.RemovePGPKey(c.User.ID, args[0]); err != nil { | 93 | if err := c.Store.RemovePGPKey(c.User.ID, args[0]); err != nil { |
| 94 | if errors.Is(err, store.ErrNotFound) { | 94 | if errors.Is(err, store.ErrNotFound) { |
| @@ -140,7 +140,7 @@ func runRepoLog(c *Ctx, args []string) int { | |||
| 140 | limit = n | 140 | limit = n |
| 141 | } | 141 | } |
| 142 | if path == "" { | 142 | if path == "" { |
| 143 | return c.fail(protocol.ExitUsage, "usage: repo log <owner/name> [--ref <r>] [--limit n] [--path <file>]") | 143 | return c.usage() |
| 144 | } | 144 | } |
| 145 | repo, code := resolveRepo(c, path, policy.CanRead) | 145 | repo, code := resolveRepo(c, path, policy.CanRead) |
| 146 | if code >= 0 { | 146 | if code >= 0 { |
| @@ -221,9 +221,8 @@ func runRepoLog(c *Ctx, args []string) int { | |||
| 221 | // statuses, and its patch. The web's commit page read these straight from | 221 | // statuses, and its patch. The web's commit page read these straight from |
| 222 | // git, which is why no other surface could open a commit. | 222 | // git, which is why no other surface could open a commit. |
| 223 | func runRepoCommit(c *Ctx, args []string) int { | 223 | func runRepoCommit(c *Ctx, args []string) int { |
| 224 | const usage = "repo commit <owner/name> <sha>" | ||
| 225 | if len(args) != 2 { | 224 | if len(args) != 2 { |
| 226 | return c.fail(protocol.ExitUsage, "usage: %s", usage) | 225 | return c.usage() |
| 227 | } | 226 | } |
| 228 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 227 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 229 | if code >= 0 { | 228 | if code >= 0 { |
internal/control/snippet.go +11 −13
| @@ -93,7 +93,7 @@ func snippetRef(c *Ctx, id string, write bool) (store.Snippet, int) { | |||
| 93 | return sn, c.fail(protocol.ExitNotFound, "no snippet %q", id) | 93 | return sn, c.fail(protocol.ExitNotFound, "no snippet %q", id) |
| 94 | } | 94 | } |
| 95 | if write && !policy.CanWriteSnippet(c.User, sn) { | 95 | if write && !policy.CanWriteSnippet(c.User, sn) { |
| 96 | return sn, c.fail(protocol.ExitDenied, "snippet %s belongs to %s", id, sn.OwnerName) | 96 | return sn, c.fail(protocol.ExitDenied, "snippet %s belongs to %s; only they can change it", id, sn.OwnerName) |
| 97 | } | 97 | } |
| 98 | return sn, -1 | 98 | return sn, -1 |
| 99 | } | 99 | } |
| @@ -132,14 +132,13 @@ func newSnippetID() string { | |||
| 132 | } | 132 | } |
| 133 | 133 | ||
| 134 | func runSnippetCreate(c *Ctx, args []string) int { | 134 | func runSnippetCreate(c *Ctx, args []string) int { |
| 135 | const usage = "usage: snippet create <filename> [--description <d>] [--visibility public|unlisted|private] < file" | 135 | f, err := parseFlags(args, flagSpec{Values: []string{"--description", "--visibility"}, MaxPos: 1, Usage: c.Cmd.Usage}) |
| 136 | f, err := parseFlags(args, flagSpec{Values: []string{"--description", "--visibility"}, MaxPos: 1, Usage: usage}) | ||
| 137 | if err != nil { | 136 | if err != nil { |
| 138 | return c.fail(protocol.ExitUsage, "%v", err) | 137 | return c.fail(protocol.ExitUsage, "%v", err) |
| 139 | } | 138 | } |
| 140 | name := f.pos(0) | 139 | name := f.pos(0) |
| 141 | if name == "" { | 140 | if name == "" { |
| 142 | return c.fail(protocol.ExitUsage, usage) | 141 | return c.usage() |
| 143 | } | 142 | } |
| 144 | if code := checkSnippetFileName(c, name); code >= 0 { | 143 | if code := checkSnippetFileName(c, name); code >= 0 { |
| 145 | return code | 144 | return code |
| @@ -186,7 +185,7 @@ func runSnippetCreate(c *Ctx, args []string) int { | |||
| 186 | 185 | ||
| 187 | func runSnippetShow(c *Ctx, args []string) int { | 186 | func runSnippetShow(c *Ctx, args []string) int { |
| 188 | if len(args) != 1 { | 187 | if len(args) != 1 { |
| 189 | return c.fail(protocol.ExitUsage, "usage: snippet show <id>") | 188 | return c.usage() |
| 190 | } | 189 | } |
| 191 | sn, code := snippetRef(c, args[0], false) | 190 | sn, code := snippetRef(c, args[0], false) |
| 192 | if code >= 0 { | 191 | if code >= 0 { |
| @@ -215,7 +214,7 @@ func runSnippetList(c *Ctx, args []string) int { | |||
| 215 | return code | 214 | return code |
| 216 | } | 215 | } |
| 217 | if len(rest) > 1 { | 216 | if len(rest) > 1 { |
| 218 | return c.fail(protocol.ExitUsage, "usage: snippet list [<owner>] [--limit n] [--cursor c]") | 217 | return c.usage() |
| 219 | } | 218 | } |
| 220 | owner := c.User | 219 | owner := c.User |
| 221 | if len(rest) == 1 { | 220 | if len(rest) == 1 { |
| @@ -253,13 +252,12 @@ func runSnippetList(c *Ctx, args []string) int { | |||
| 253 | } | 252 | } |
| 254 | 253 | ||
| 255 | func runSnippetEdit(c *Ctx, args []string) int { | 254 | func runSnippetEdit(c *Ctx, args []string) int { |
| 256 | const usage = "usage: snippet edit <id> [--description <d>] [--visibility public|unlisted|private]" | 255 | f, err := parseFlags(args, flagSpec{Values: []string{"--description", "--visibility"}, MaxPos: 1, Usage: c.Cmd.Usage}) |
| 257 | f, err := parseFlags(args, flagSpec{Values: []string{"--description", "--visibility"}, MaxPos: 1, Usage: usage}) | ||
| 258 | if err != nil { | 256 | if err != nil { |
| 259 | return c.fail(protocol.ExitUsage, "%v", err) | 257 | return c.fail(protocol.ExitUsage, "%v", err) |
| 260 | } | 258 | } |
| 261 | if f.pos(0) == "" || (!f.Has("--description") && !f.Has("--visibility")) { | 259 | if f.pos(0) == "" || (!f.Has("--description") && !f.Has("--visibility")) { |
| 262 | return c.fail(protocol.ExitUsage, usage) | 260 | return c.usage() |
| 263 | } | 261 | } |
| 264 | sn, code := snippetRef(c, f.pos(0), true) | 262 | sn, code := snippetRef(c, f.pos(0), true) |
| 265 | if code >= 0 { | 263 | if code >= 0 { |
| @@ -289,7 +287,7 @@ func runSnippetEdit(c *Ctx, args []string) int { | |||
| 289 | 287 | ||
| 290 | func runSnippetDelete(c *Ctx, args []string) int { | 288 | func runSnippetDelete(c *Ctx, args []string) int { |
| 291 | if len(args) != 1 { | 289 | if len(args) != 1 { |
| 292 | return c.fail(protocol.ExitUsage, "usage: snippet delete <id>") | 290 | return c.usage() |
| 293 | } | 291 | } |
| 294 | sn, code := snippetRef(c, args[0], true) | 292 | sn, code := snippetRef(c, args[0], true) |
| 295 | if code >= 0 { | 293 | if code >= 0 { |
| @@ -305,7 +303,7 @@ func runSnippetDelete(c *Ctx, args []string) int { | |||
| 305 | 303 | ||
| 306 | func runSnippetFileSet(c *Ctx, args []string) int { | 304 | func runSnippetFileSet(c *Ctx, args []string) int { |
| 307 | if len(args) != 2 { | 305 | if len(args) != 2 { |
| 308 | return c.fail(protocol.ExitUsage, "usage: snippet file set <id> <filename> < file") | 306 | return c.usage() |
| 309 | } | 307 | } |
| 310 | sn, code := snippetRef(c, args[0], true) | 308 | sn, code := snippetRef(c, args[0], true) |
| 311 | if code >= 0 { | 309 | if code >= 0 { |
| @@ -336,7 +334,7 @@ func runSnippetFileSet(c *Ctx, args []string) int { | |||
| 336 | 334 | ||
| 337 | func runSnippetFileGet(c *Ctx, args []string) int { | 335 | func runSnippetFileGet(c *Ctx, args []string) int { |
| 338 | if len(args) != 2 { | 336 | if len(args) != 2 { |
| 339 | return c.fail(protocol.ExitUsage, "usage: snippet file get <id> <filename> > file") | 337 | return c.usage() |
| 340 | } | 338 | } |
| 341 | sn, code := snippetRef(c, args[0], false) | 339 | sn, code := snippetRef(c, args[0], false) |
| 342 | if code >= 0 { | 340 | if code >= 0 { |
| @@ -360,7 +358,7 @@ func runSnippetFileGet(c *Ctx, args []string) int { | |||
| 360 | 358 | ||
| 361 | func runSnippetFileRemove(c *Ctx, args []string) int { | 359 | func runSnippetFileRemove(c *Ctx, args []string) int { |
| 362 | if len(args) != 2 { | 360 | if len(args) != 2 { |
| 363 | return c.fail(protocol.ExitUsage, "usage: snippet file remove <id> <filename>") | 361 | return c.usage() |
| 364 | } | 362 | } |
| 365 | sn, code := snippetRef(c, args[0], true) | 363 | sn, code := snippetRef(c, args[0], true) |
| 366 | if code >= 0 { | 364 | if code >= 0 { |
internal/control/status.go +4 −8
| @@ -55,7 +55,7 @@ func runStatusSet(c *Ctx, args []string) int { | |||
| 55 | } | 55 | } |
| 56 | } | 56 | } |
| 57 | if path == "" || sha == "" || context == "" || !validStatusState[state] { | 57 | if path == "" || sha == "" || context == "" || !validStatusState[state] { |
| 58 | return c.fail(protocol.ExitUsage, "usage: status set <owner/name> <sha> --context <c> --state pending|success|failure|error") | 58 | return c.usage() |
| 59 | } | 59 | } |
| 60 | if url != "" && !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { | 60 | if url != "" && !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { |
| 61 | return c.fail(protocol.ExitUsage, "--url must be http(s)") | 61 | return c.fail(protocol.ExitUsage, "--url must be http(s)") |
| @@ -86,7 +86,7 @@ func runStatusSet(c *Ctx, args []string) int { | |||
| 86 | 86 | ||
| 87 | func runStatusList(c *Ctx, args []string) int { | 87 | func runStatusList(c *Ctx, args []string) int { |
| 88 | if len(args) != 2 { | 88 | if len(args) != 2 { |
| 89 | return c.fail(protocol.ExitUsage, "usage: status list <owner/name> <sha>") | 89 | return c.usage() |
| 90 | } | 90 | } |
| 91 | repo, code := resolveRepo(c, args[0], policy.CanRead) | 91 | repo, code := resolveRepo(c, args[0], policy.CanRead) |
| 92 | if code >= 0 { | 92 | if code >= 0 { |
| @@ -118,13 +118,9 @@ func runStatusList(c *Ctx, args []string) int { | |||
| 118 | Statuses []out `json:"statuses"` | 118 | Statuses []out `json:"statuses"` |
| 119 | }{full, combinedOf(statuses), ds} | 119 | }{full, combinedOf(statuses), ds} |
| 120 | return c.emit(d, func(w io.Writer) { | 120 | return c.emit(d, func(w io.Writer) { |
| 121 | fmt.Fprintf(w, "%.10s: %s\n", d.SHA, orNone(d.Combined)) | 121 | fmt.Fprintf(w, "combined\t%s\t%.10s\n", orNone(d.Combined), d.SHA) |
| 122 | for _, x := range ds { | 122 | for _, x := range ds { |
| 123 | extra := "" | 123 | fmt.Fprintf(w, "%s\t%s\t%s\n", x.Context, x.State, x.Description) |
| 124 | if x.Description != "" { | ||
| 125 | extra = "\t" + x.Description | ||
| 126 | } | ||
| 127 | fmt.Fprintf(w, " %s\t%s%s\n", x.Context, x.State, extra) | ||
| 128 | } | 124 | } |
| 129 | }) | 125 | }) |
| 130 | } | 126 | } |
internal/control/teams.go +10 −10
| @@ -53,7 +53,7 @@ func orgAdminRef(c *Ctx, name string) (store.Org, int) { | |||
| 53 | return org, c.fail(protocol.ExitFailure, "%v", err) | 53 | return org, c.fail(protocol.ExitFailure, "%v", err) |
| 54 | } | 54 | } |
| 55 | if role != "admin" { | 55 | if role != "admin" { |
| 56 | return org, c.fail(protocol.ExitDenied, "only admins of %s can manage teams", name) | 56 | return org, c.fail(protocol.ExitDenied, "only admins of %s can manage teams; ask one to add you", name) |
| 57 | } | 57 | } |
| 58 | return org, -1 | 58 | return org, -1 |
| 59 | } | 59 | } |
| @@ -71,7 +71,7 @@ func orgMemberRef(c *Ctx, name string) (store.Org, int) { | |||
| 71 | return org, c.fail(protocol.ExitFailure, "%v", err) | 71 | return org, c.fail(protocol.ExitFailure, "%v", err) |
| 72 | } | 72 | } |
| 73 | if role == "" { | 73 | if role == "" { |
| 74 | return org, c.fail(protocol.ExitDenied, "teams of %s are visible to its members", name) | 74 | return org, c.fail(protocol.ExitDenied, "teams of %s are visible to its members; ask an admin to add you", name) |
| 75 | } | 75 | } |
| 76 | return org, -1 | 76 | return org, -1 |
| 77 | } | 77 | } |
| @@ -89,7 +89,7 @@ func teamRef(c *Ctx, org store.Org, name string) (store.Team, int) { | |||
| 89 | 89 | ||
| 90 | func runTeamCreate(c *Ctx, args []string) int { | 90 | func runTeamCreate(c *Ctx, args []string) int { |
| 91 | if len(args) != 2 { | 91 | if len(args) != 2 { |
| 92 | return c.fail(protocol.ExitUsage, "usage: org team create <org> <team>") | 92 | return c.usage() |
| 93 | } | 93 | } |
| 94 | org, code := orgAdminRef(c, args[0]) | 94 | org, code := orgAdminRef(c, args[0]) |
| 95 | if code >= 0 { | 95 | if code >= 0 { |
| @@ -108,7 +108,7 @@ func runTeamCreate(c *Ctx, args []string) int { | |||
| 108 | 108 | ||
| 109 | func runTeamDelete(c *Ctx, args []string) int { | 109 | func runTeamDelete(c *Ctx, args []string) int { |
| 110 | if len(args) != 2 { | 110 | if len(args) != 2 { |
| 111 | return c.fail(protocol.ExitUsage, "usage: org team delete <org> <team>") | 111 | return c.usage() |
| 112 | } | 112 | } |
| 113 | org, code := orgAdminRef(c, args[0]) | 113 | org, code := orgAdminRef(c, args[0]) |
| 114 | if code >= 0 { | 114 | if code >= 0 { |
| @@ -128,7 +128,7 @@ func runTeamDelete(c *Ctx, args []string) int { | |||
| 128 | 128 | ||
| 129 | func runTeamList(c *Ctx, args []string) int { | 129 | func runTeamList(c *Ctx, args []string) int { |
| 130 | if len(args) != 1 { | 130 | if len(args) != 1 { |
| 131 | return c.fail(protocol.ExitUsage, "usage: org team list <org>") | 131 | return c.usage() |
| 132 | } | 132 | } |
| 133 | org, code := orgMemberRef(c, args[0]) | 133 | org, code := orgMemberRef(c, args[0]) |
| 134 | if code >= 0 { | 134 | if code >= 0 { |
| @@ -151,7 +151,7 @@ func runTeamList(c *Ctx, args []string) int { | |||
| 151 | 151 | ||
| 152 | func runTeamShow(c *Ctx, args []string) int { | 152 | func runTeamShow(c *Ctx, args []string) int { |
| 153 | if len(args) != 2 { | 153 | if len(args) != 2 { |
| 154 | return c.fail(protocol.ExitUsage, "usage: org team show <org> <team>") | 154 | return c.usage() |
| 155 | } | 155 | } |
| 156 | org, code := orgMemberRef(c, args[0]) | 156 | org, code := orgMemberRef(c, args[0]) |
| 157 | if code >= 0 { | 157 | if code >= 0 { |
| @@ -191,7 +191,7 @@ func editTeamMembers(c *Ctx, args []string, add bool) int { | |||
| 191 | verb = "remove" | 191 | verb = "remove" |
| 192 | } | 192 | } |
| 193 | if len(args) < 3 { | 193 | if len(args) < 3 { |
| 194 | return c.fail(protocol.ExitUsage, "usage: org team %s <org> <team> <user>...", verb) | 194 | return c.usage() |
| 195 | } | 195 | } |
| 196 | org, code := orgAdminRef(c, args[0]) | 196 | org, code := orgAdminRef(c, args[0]) |
| 197 | if code >= 0 { | 197 | if code >= 0 { |
| @@ -233,7 +233,7 @@ func editTeamMembers(c *Ctx, args []string, add bool) int { | |||
| 233 | 233 | ||
| 234 | func runTeamGrant(c *Ctx, args []string) int { | 234 | func runTeamGrant(c *Ctx, args []string) int { |
| 235 | if len(args) != 4 || !slices.Contains([]string{"read", "write", "admin"}, args[3]) { | 235 | if len(args) != 4 || !slices.Contains([]string{"read", "write", "admin"}, args[3]) { |
| 236 | return c.fail(protocol.ExitUsage, "usage: org team grant <org> <team> <owner/name> read|write|admin") | 236 | return c.usage() |
| 237 | } | 237 | } |
| 238 | org, code := orgAdminRef(c, args[0]) | 238 | org, code := orgAdminRef(c, args[0]) |
| 239 | if code >= 0 { | 239 | if code >= 0 { |
| @@ -257,7 +257,7 @@ func runTeamGrant(c *Ctx, args []string) int { | |||
| 257 | 257 | ||
| 258 | func runTeamRevoke(c *Ctx, args []string) int { | 258 | func runTeamRevoke(c *Ctx, args []string) int { |
| 259 | if len(args) != 3 { | 259 | if len(args) != 3 { |
| 260 | return c.fail(protocol.ExitUsage, "usage: org team revoke <org> <team> <owner/name>") | 260 | return c.usage() |
| 261 | } | 261 | } |
| 262 | org, code := orgAdminRef(c, args[0]) | 262 | org, code := orgAdminRef(c, args[0]) |
| 263 | if code >= 0 { | 263 | if code >= 0 { |
| @@ -284,7 +284,7 @@ func runTeamRevoke(c *Ctx, args []string) int { | |||
| 284 | 284 | ||
| 285 | func runOrgMembersRole(c *Ctx, args []string) int { | 285 | func runOrgMembersRole(c *Ctx, args []string) int { |
| 286 | if len(args) != 2 || !slices.Contains([]string{"write", "read", "none"}, args[1]) { | 286 | if len(args) != 2 || !slices.Contains([]string{"write", "read", "none"}, args[1]) { |
| 287 | return c.fail(protocol.ExitUsage, "usage: org settings members-role <org> write|read|none") | 287 | return c.usage() |
| 288 | } | 288 | } |
| 289 | org, code := orgAdminRef(c, args[0]) | 289 | org, code := orgAdminRef(c, args[0]) |
| 290 | if code >= 0 { | 290 | if code >= 0 { |
internal/control/token.go +2 −2
| @@ -48,7 +48,7 @@ func runTokenCreate(c *Ctx, args []string) int { | |||
| 48 | scope = f.Value("--scope") | 48 | scope = f.Value("--scope") |
| 49 | } | 49 | } |
| 50 | if name == "" || (scope != "full" && scope != "read") { | 50 | if name == "" || (scope != "full" && scope != "read") { |
| 51 | return c.fail(protocol.ExitUsage, "usage: token create --name <n> [--scope full|read] [--ttl 30d]") | 51 | return c.usage() |
| 52 | } | 52 | } |
| 53 | var expires *time.Time | 53 | var expires *time.Time |
| 54 | if ttl != "" { | 54 | if ttl != "" { |
| @@ -108,7 +108,7 @@ func runTokenList(c *Ctx, args []string) int { | |||
| 108 | 108 | ||
| 109 | func runTokenRevoke(c *Ctx, args []string) int { | 109 | func runTokenRevoke(c *Ctx, args []string) int { |
| 110 | if len(args) != 1 { | 110 | if len(args) != 1 { |
| 111 | return c.fail(protocol.ExitUsage, "usage: token revoke <name>") | 111 | return c.usage() |
| 112 | } | 112 | } |
| 113 | if err := c.Store.RevokeAPIToken(c.User.ID, args[0]); err != nil { | 113 | if err := c.Store.RevokeAPIToken(c.User.ID, args[0]); err != nil { |
| 114 | if errors.Is(err, store.ErrNotFound) { | 114 | if errors.Is(err, store.ErrNotFound) { |
internal/control/web.go +3 −3
| @@ -26,7 +26,7 @@ func init() { | |||
| 26 | 26 | ||
| 27 | func runWebSessionsList(c *Ctx, args []string) int { | 27 | func runWebSessionsList(c *Ctx, args []string) int { |
| 28 | if len(args) != 0 { | 28 | if len(args) != 0 { |
| 29 | return c.fail(protocol.ExitUsage, "usage: web sessions list") | 29 | return c.usage() |
| 30 | } | 30 | } |
| 31 | sessions, err := c.Store.ListWebSessions(c.User.ID) | 31 | sessions, err := c.Store.ListWebSessions(c.User.ID) |
| 32 | if err != nil { | 32 | if err != nil { |
| @@ -41,7 +41,7 @@ func runWebSessionsList(c *Ctx, args []string) int { | |||
| 41 | 41 | ||
| 42 | func runWebSessionsRevoke(c *Ctx, args []string) int { | 42 | func runWebSessionsRevoke(c *Ctx, args []string) int { |
| 43 | if len(args) != 1 { | 43 | if len(args) != 1 { |
| 44 | return c.fail(protocol.ExitUsage, "usage: web sessions revoke <id>|--all") | 44 | return c.usage() |
| 45 | } | 45 | } |
| 46 | if args[0] == "--all" { | 46 | if args[0] == "--all" { |
| 47 | n, err := c.Store.RevokeAllWebSessions(c.User.ID) | 47 | n, err := c.Store.RevokeAllWebSessions(c.User.ID) |
| @@ -65,7 +65,7 @@ func runWebSessionsRevoke(c *Ctx, args []string) int { | |||
| 65 | 65 | ||
| 66 | func runWebLogin(c *Ctx, args []string) int { | 66 | func runWebLogin(c *Ctx, args []string) int { |
| 67 | if len(args) != 0 { | 67 | if len(args) != 0 { |
| 68 | return c.fail(protocol.ExitUsage, "usage: web login [--json]") | 68 | return c.usage() |
| 69 | } | 69 | } |
| 70 | if c.Cfg.Web.Mode != "accounts" { | 70 | if c.Cfg.Web.Mode != "accounts" { |
| 71 | return c.fail(protocol.ExitDenied, | 71 | return c.fail(protocol.ExitDenied, |
internal/control/webhook.go +5 −5
| @@ -40,7 +40,7 @@ func runWebhookAdd(c *Ctx, args []string) int { | |||
| 40 | events = f.Value("--events") | 40 | events = f.Value("--events") |
| 41 | } | 41 | } |
| 42 | if path == "" || url == "" { | 42 | if path == "" || url == "" { |
| 43 | return c.fail(protocol.ExitUsage, "usage: webhook add <owner/name> <url> [--secret <s>] [--events <k1,k2>|*]") | 43 | return c.usage() |
| 44 | } | 44 | } |
| 45 | repo, code := resolveRepo(c, path, policy.CanAdmin) | 45 | repo, code := resolveRepo(c, path, policy.CanAdmin) |
| 46 | if code >= 0 { | 46 | if code >= 0 { |
| @@ -68,7 +68,7 @@ func runWebhookAdd(c *Ctx, args []string) int { | |||
| 68 | 68 | ||
| 69 | func runWebhookList(c *Ctx, args []string) int { | 69 | func runWebhookList(c *Ctx, args []string) int { |
| 70 | if len(args) != 1 { | 70 | if len(args) != 1 { |
| 71 | return c.fail(protocol.ExitUsage, "usage: webhook list <owner/name>") | 71 | return c.usage() |
| 72 | } | 72 | } |
| 73 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 73 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 74 | if code >= 0 { | 74 | if code >= 0 { |
| @@ -98,7 +98,7 @@ func runWebhookList(c *Ctx, args []string) int { | |||
| 98 | 98 | ||
| 99 | func runWebhookRemove(c *Ctx, args []string) int { | 99 | func runWebhookRemove(c *Ctx, args []string) int { |
| 100 | if len(args) != 2 { | 100 | if len(args) != 2 { |
| 101 | return c.fail(protocol.ExitUsage, "usage: webhook remove <owner/name> <id>") | 101 | return c.usage() |
| 102 | } | 102 | } |
| 103 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 103 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 104 | if code >= 0 { | 104 | if code >= 0 { |
| @@ -133,7 +133,7 @@ func runWebhookDeliveries(c *Ctx, args []string) int { | |||
| 133 | limit = n | 133 | limit = n |
| 134 | } | 134 | } |
| 135 | if path == "" { | 135 | if path == "" { |
| 136 | return c.fail(protocol.ExitUsage, "usage: webhook deliveries <owner/name> [--limit n]") | 136 | return c.usage() |
| 137 | } | 137 | } |
| 138 | repo, code := resolveRepo(c, path, policy.CanAdmin) | 138 | repo, code := resolveRepo(c, path, policy.CanAdmin) |
| 139 | if code >= 0 { | 139 | if code >= 0 { |
| @@ -169,7 +169,7 @@ func runWebhookDeliveries(c *Ctx, args []string) int { | |||
| 169 | 169 | ||
| 170 | func runWebhookRedeliver(c *Ctx, args []string) int { | 170 | func runWebhookRedeliver(c *Ctx, args []string) int { |
| 171 | if len(args) != 2 { | 171 | if len(args) != 2 { |
| 172 | return c.fail(protocol.ExitUsage, "usage: webhook redeliver <owner/name> <delivery-id>") | 172 | return c.usage() |
| 173 | } | 173 | } |
| 174 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) | 174 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 175 | if code >= 0 { | 175 | if code >= 0 { |
internal/control/wiki.go +2 −2
| @@ -79,7 +79,7 @@ func wikiPages(dir, branch string) []string { | |||
| 79 | 79 | ||
| 80 | func runWikiList(c *Ctx, args []string) int { | 80 | func runWikiList(c *Ctx, args []string) int { |
| 81 | if len(args) != 1 { | 81 | if len(args) != 1 { |
| 82 | return c.fail(protocol.ExitUsage, "usage: wiki list <owner/name>") | 82 | return c.usage() |
| 83 | } | 83 | } |
| 84 | repo, dir, branch, code := wikiDir(c, args[0]) | 84 | repo, dir, branch, code := wikiDir(c, args[0]) |
| 85 | if code >= 0 { | 85 | if code >= 0 { |
| @@ -120,7 +120,7 @@ func wikiHome(pages []string) string { | |||
| 120 | 120 | ||
| 121 | func runWikiShow(c *Ctx, args []string) int { | 121 | func runWikiShow(c *Ctx, args []string) int { |
| 122 | if len(args) < 1 || len(args) > 2 { | 122 | if len(args) < 1 || len(args) > 2 { |
| 123 | return c.fail(protocol.ExitUsage, "usage: wiki show <owner/name> [<page>]") | 123 | return c.usage() |
| 124 | } | 124 | } |
| 125 | repo, dir, branch, code := wikiDir(c, args[0]) | 125 | repo, dir, branch, code := wikiDir(c, args[0]) |
| 126 | if code >= 0 { | 126 | if code >= 0 { |