Commit 94884a2232
Verified · cmc ci/build: success ci/test: success ci/vuln: success
Layout: unified · split
internal/control/admin.go +13 −30
| @@ -91,19 +91,12 @@ func runAdminUserList(c *Ctx, args []string) int { | ||
| 91 | 91 | if code >= 0 { |
| 92 | 92 | return code |
| 93 | 93 | } |
| 94 | state := "" | |
| 95 | for i := 0; i < len(args); i++ { | |
| 96 | switch args[i] { | |
| 97 | case "--state": | |
| 98 | if i+1 >= len(args) { | |
| 99 | return c.fail(protocol.ExitUsage, "--state requires active|pending|disabled|admin") | |
| 100 | } | |
| 101 | state = args[i+1] | |
| 102 | i++ | |
| 103 | default: | |
| 104 | return c.fail(protocol.ExitUsage, "usage: admin user list [--state active|pending|disabled|admin] [--limit <n>] [--cursor <c>]") | |
| 105 | } | |
| 94 | f, err := parseFlags(args, flagSpec{Values: []string{"--state"}, MaxPos: 0, | |
| 95 | Usage: "admin user list [--state active|pending|disabled|admin] [--limit <n>] [--cursor <c>]"}) | |
| 96 | if err != nil { | |
| 97 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 106 | 98 | } |
| 99 | state := f.Value("--state") | |
| 107 | 100 | switch state { |
| 108 | 101 | case "", "active", "pending", "disabled", "admin": |
| 109 | 102 | default: |
| @@ -344,24 +337,14 @@ func runAdminRepoList(c *Ctx, args []string) int { | ||
| 344 | 337 | if code >= 0 { |
| 345 | 338 | return code |
| 346 | 339 | } |
| 347 | var owner, visibility string | |
| 348 | for i := 0; i < len(args); i++ { | |
| 349 | switch args[i] { | |
| 350 | case "--owner": | |
| 351 | if i+1 >= len(args) { | |
| 352 | return c.fail(protocol.ExitUsage, "--owner requires a value") | |
| 353 | } | |
| 354 | owner = args[i+1] | |
| 355 | i++ | |
| 356 | case "--visibility": | |
| 357 | if i+1 >= len(args) || (args[i+1] != "public" && args[i+1] != "private") { | |
| 358 | return c.fail(protocol.ExitUsage, "--visibility requires public|private") | |
| 359 | } | |
| 360 | visibility = args[i+1] | |
| 361 | i++ | |
| 362 | default: | |
| 363 | return c.fail(protocol.ExitUsage, "usage: admin repo list [--owner <name>] [--visibility public|private] [--limit <n>] [--cursor <c>]") | |
| 364 | } | |
| 340 | f, err := parseFlags(args, flagSpec{Values: []string{"--owner", "--visibility"}, MaxPos: 0, | |
| 341 | Usage: "admin repo list [--owner <name>] [--visibility public|private] [--limit <n>] [--cursor <c>]"}) | |
| 342 | if err != nil { | |
| 343 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 344 | } | |
| 345 | owner, visibility := f.Value("--owner"), f.Value("--visibility") | |
| 346 | if visibility != "" && visibility != "public" && visibility != "private" { | |
| 347 | return c.fail(protocol.ExitUsage, "--visibility requires public|private") | |
| 365 | 348 | } |
| 366 | 349 | repos, err := c.Store.ListReposAdmin(owner, visibility, p.queryLimit(), p.key) |
| 367 | 350 | if err != nil { |
internal/control/adminhost.go +11 −26
| @@ -56,32 +56,17 @@ func runAdminUserCreate(c *Ctx, args []string) int { | ||
| 56 | 56 | return code |
| 57 | 57 | } |
| 58 | 58 | const usage = "usage: admin user create <username> [--admin] [--email <address> [--verified]] [--key -] < key.pub" |
| 59 | var username, email string | |
| 60 | var isAdmin, verified, withKey bool | |
| 61 | for i := 0; i < len(args); i++ { | |
| 62 | switch args[i] { | |
| 63 | case "--admin": | |
| 64 | isAdmin = true | |
| 65 | case "--verified": | |
| 66 | verified = true | |
| 67 | case "--email": | |
| 68 | if i+1 >= len(args) { | |
| 69 | return c.fail(protocol.ExitUsage, "--email requires a value") | |
| 70 | } | |
| 71 | email = args[i+1] | |
| 72 | i++ | |
| 73 | case "--key": | |
| 74 | if i+1 >= len(args) || args[i+1] != "-" { | |
| 75 | return c.fail(protocol.ExitUsage, "--key only supports - (the public key on stdin)") | |
| 76 | } | |
| 77 | withKey = true | |
| 78 | i++ | |
| 79 | default: | |
| 80 | if username != "" || len(args[i]) == 0 || args[i][0] == '-' { | |
| 81 | return c.fail(protocol.ExitUsage, usage) | |
| 82 | } | |
| 83 | username = args[i] | |
| 84 | } | |
| 59 | f, err := parseFlags(args, flagSpec{Values: []string{"--email", "--key"}, Bools: []string{"--admin", "--verified"}, MaxPos: 1, Usage: usage}) | |
| 60 | if err != nil { | |
| 61 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 62 | } | |
| 63 | username, email := f.pos(0), f.Value("--email") | |
| 64 | isAdmin, verified, withKey := f.Has("--admin"), f.Has("--verified"), f.Has("--key") | |
| 65 | if withKey && f.Value("--key") != "-" { | |
| 66 | return c.fail(protocol.ExitUsage, "--key only supports - (the public key on stdin)") | |
| 67 | } | |
| 68 | if username == "" || username[0] == '-' { | |
| 69 | return c.fail(protocol.ExitUsage, usage) | |
| 85 | 70 | } |
| 86 | 71 | if username == "" || (verified && email == "") { |
| 87 | 72 | return c.fail(protocol.ExitUsage, usage) |
internal/control/audit.go +16 −24
| @@ -25,32 +25,24 @@ func runAudit(c *Ctx, args []string) int { | ||
| 25 | 25 | return c.fail(protocol.ExitDenied, "the audit log is for instance admins") |
| 26 | 26 | } |
| 27 | 27 | f := store.AuditFilter{Limit: 100} |
| 28 | for i := 0; i < len(args); i++ { | |
| 29 | if i+1 >= len(args) { | |
| 30 | return c.fail(protocol.ExitUsage, auditUsage) | |
| 28 | fl, err := parseFlags(args, flagSpec{Values: []string{"--limit", "--actor", "--action", "--since"}, MaxPos: 0, Usage: auditUsage}) | |
| 29 | if err != nil { | |
| 30 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 31 | } | |
| 32 | if fl.Has("--limit") { | |
| 33 | n, err := strconv.Atoi(fl.Value("--limit")) | |
| 34 | if err != nil || n < 1 || n > 10000 { | |
| 35 | return c.fail(protocol.ExitUsage, "--limit must be 1 to 10000") | |
| 31 | 36 | } |
| 32 | v := args[i+1] | |
| 33 | switch args[i] { | |
| 34 | case "--limit": | |
| 35 | n, err := strconv.Atoi(v) | |
| 36 | if err != nil || n < 1 || n > 10000 { | |
| 37 | return c.fail(protocol.ExitUsage, "--limit must be 1 to 10000") | |
| 38 | } | |
| 39 | f.Limit = n | |
| 40 | case "--actor": | |
| 41 | f.Actor = v | |
| 42 | case "--action": | |
| 43 | f.ActionPrefix = v | |
| 44 | case "--since": | |
| 45 | t, ok := parseSince(v, time.Now()) | |
| 46 | if !ok { | |
| 47 | return c.fail(protocol.ExitUsage, "--since takes a duration (30m, 24h, 7d) or a date (2026-09-01, RFC 3339)") | |
| 48 | } | |
| 49 | f.Since = t.UTC().Format("2006-01-02T15:04:05.000Z") | |
| 50 | default: | |
| 51 | return c.fail(protocol.ExitUsage, auditUsage) | |
| 37 | f.Limit = n | |
| 38 | } | |
| 39 | f.Actor, f.ActionPrefix = fl.Value("--actor"), fl.Value("--action") | |
| 40 | if fl.Has("--since") { | |
| 41 | t, ok := parseSince(fl.Value("--since"), time.Now()) | |
| 42 | if !ok { | |
| 43 | return c.fail(protocol.ExitUsage, "--since takes a duration (30m, 24h, 7d) or a date (2026-09-01, RFC 3339)") | |
| 52 | 44 | } |
| 53 | i++ | |
| 45 | f.Since = t.UTC().Format("2006-01-02T15:04:05.000Z") | |
| 54 | 46 | } |
| 55 | 47 | entries, err := c.Store.AuditEntries(f) |
| 56 | 48 | if err != nil { |
internal/control/commitfile.go +5 −23
| @@ -35,30 +35,12 @@ const maxCommitFileBytes = 1 << 20 | ||
| 35 | 35 | // command rather than writing a commit its own policy would reject. |
| 36 | 36 | func runCommitFile(c *Ctx, args []string) int { |
| 37 | 37 | const usage = "repo commit-file <owner/name> <path> --ref <branch> [--message <m>] [--file -]" |
| 38 | var rest []string | |
| 39 | var ref, message, file string | |
| 40 | for i := 0; i < len(args); i++ { | |
| 41 | switch args[i] { | |
| 42 | case "--ref", "--message", "--file": | |
| 43 | if i+1 >= len(args) { | |
| 44 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 45 | } | |
| 46 | switch args[i] { | |
| 47 | case "--ref": | |
| 48 | ref = args[i+1] | |
| 49 | case "--message": | |
| 50 | message = args[i+1] | |
| 51 | case "--file": | |
| 52 | file = args[i+1] | |
| 53 | } | |
| 54 | i++ | |
| 55 | default: | |
| 56 | if strings.HasPrefix(args[i], "--") { | |
| 57 | return c.fail(protocol.ExitUsage, "unknown flag %q\nusage: %s", args[i], usage) | |
| 58 | } | |
| 59 | rest = append(rest, args[i]) | |
| 60 | } | |
| 38 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref", "--message", "--file"}, MaxPos: -1, Usage: usage}) | |
| 39 | if err != nil { | |
| 40 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 61 | 41 | } |
| 42 | rest := f.Pos | |
| 43 | ref, message, file := f.Value("--ref"), f.Value("--message"), f.Value("--file") | |
| 62 | 44 | if len(rest) != 2 || ref == "" { |
| 63 | 45 | return c.fail(protocol.ExitUsage, "usage: %s", usage) |
| 64 | 46 | } |
internal/control/diffcomment.go +19 −35
| @@ -31,43 +31,27 @@ func init() { | ||
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | func runDiffComment(c *Ctx, args []string) int { |
| 34 | var rest []string | |
| 35 | var path, message, file string | |
| 34 | f, err := parseFlags(args, flagSpec{Values: []string{"--path", "--line", "--reply", "--message", "--file"}, Bools: []string{"--old"}, MaxPos: -1, | |
| 35 | Usage: "mr diff-comment <owner/name> <n> --path <file> --line <l> [--old] [--reply <id>] [--message <m> | --file -]"}) | |
| 36 | if err != nil { | |
| 37 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 38 | } | |
| 39 | rest := f.Pos | |
| 40 | path, message, file, old := f.Value("--path"), f.Value("--message"), f.Value("--file"), f.Has("--old") | |
| 36 | 41 | var line, replyTo int64 |
| 37 | old := false | |
| 38 | for i := 0; i < len(args); i++ { | |
| 39 | switch args[i] { | |
| 40 | case "--path", "--line", "--reply", "--message", "--file": | |
| 41 | if i+1 >= len(args) { | |
| 42 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 43 | } | |
| 44 | v := args[i+1] | |
| 45 | switch args[i] { | |
| 46 | case "--path": | |
| 47 | path = v | |
| 48 | case "--line": | |
| 49 | n, err := strconv.ParseInt(v, 10, 64) | |
| 50 | if err != nil || n < 1 { | |
| 51 | return c.fail(protocol.ExitUsage, "--line must be a positive number") | |
| 52 | } | |
| 53 | line = n | |
| 54 | case "--reply": | |
| 55 | n, err := strconv.ParseInt(v, 10, 64) | |
| 56 | if err != nil || n < 1 { | |
| 57 | return c.fail(protocol.ExitUsage, "--reply must be a thread id") | |
| 58 | } | |
| 59 | replyTo = n | |
| 60 | case "--message": | |
| 61 | message = v | |
| 62 | case "--file": | |
| 63 | file = v | |
| 64 | } | |
| 65 | i++ | |
| 66 | case "--old": | |
| 67 | old = true | |
| 68 | default: | |
| 69 | rest = append(rest, args[i]) | |
| 42 | if f.Has("--line") { | |
| 43 | n, err := strconv.ParseInt(f.Value("--line"), 10, 64) | |
| 44 | if err != nil || n < 1 { | |
| 45 | return c.fail(protocol.ExitUsage, "--line must be a positive number") | |
| 46 | } | |
| 47 | line = n | |
| 48 | } | |
| 49 | if f.Has("--reply") { | |
| 50 | n, err := strconv.ParseInt(f.Value("--reply"), 10, 64) | |
| 51 | if err != nil || n < 1 { | |
| 52 | return c.fail(protocol.ExitUsage, "--reply must be a thread id") | |
| 70 | 53 | } |
| 54 | replyTo = n | |
| 71 | 55 | } |
| 72 | 56 | repo, mr, code := mrRef(c, rest, policy.CanRead) |
| 73 | 57 | if code >= 0 { |
internal/control/explore.go +4 −13
| @@ -80,20 +80,11 @@ func runExplore(c *Ctx, args []string) int { | ||
| 80 | 80 | // same bytes with a Content-Disposition on them. |
| 81 | 81 | func runRepoDownload(c *Ctx, args []string) int { |
| 82 | 82 | const usage = "repo download <owner/name> [--ref <r>] > repo.tar.gz" |
| 83 | var rest []string | |
| 84 | var ref string | |
| 85 | for i := 0; i < len(args); i++ { | |
| 86 | switch args[i] { | |
| 87 | case "--ref": | |
| 88 | if i+1 >= len(args) { | |
| 89 | return c.fail(protocol.ExitUsage, "--ref requires a value") | |
| 90 | } | |
| 91 | ref = args[i+1] | |
| 92 | i++ | |
| 93 | default: | |
| 94 | rest = append(rest, args[i]) | |
| 95 | } | |
| 83 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref"}, MaxPos: -1, Usage: usage}) | |
| 84 | if err != nil { | |
| 85 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 96 | 86 | } |
| 87 | rest, ref := f.Pos, f.Value("--ref") | |
| 97 | 88 | if len(rest) != 1 { |
| 98 | 89 | return c.fail(protocol.ExitUsage, "usage: %s", usage) |
| 99 | 90 | } |
internal/control/flags.go added +101
| @@ -0,0 +1,101 @@ | ||
| 1 | package control | |
| 2 | ||
| 3 | import ( | |
| 4 | "fmt" | |
| 5 | "strings" | |
| 6 | ) | |
| 7 | ||
| 8 | // flagSpec is what a command accepts: flags that take one value, flags | |
| 9 | // that take a value and may repeat, switches, and how many positional | |
| 10 | // arguments are allowed (-1 for any number). Every command used to walk | |
| 11 | // argv by hand and decided on its own whether an unknown --flag was an | |
| 12 | // error, a positional or nothing at all (#96); parseFlags decides once. | |
| 13 | type flagSpec struct { | |
| 14 | Values []string | |
| 15 | Multi []string | |
| 16 | Bools []string | |
| 17 | MaxPos int | |
| 18 | Usage string | |
| 19 | } | |
| 20 | ||
| 21 | // flags is a parsed argv: positionals in order, and each flag by name. | |
| 22 | type flags struct { | |
| 23 | Pos []string | |
| 24 | vals map[string]string | |
| 25 | multi map[string][]string | |
| 26 | seen map[string]bool | |
| 27 | } | |
| 28 | ||
| 29 | // Value is the last value given for a value flag, or "". | |
| 30 | func (f flags) Value(name string) string { return f.vals[name] } | |
| 31 | ||
| 32 | // Has reports whether a flag of any kind was given. | |
| 33 | func (f flags) Has(name string) bool { return f.seen[name] } | |
| 34 | ||
| 35 | // List is every value given for a repeatable flag, in order. | |
| 36 | func (f flags) List(name string) []string { return f.multi[name] } | |
| 37 | ||
| 38 | // parseFlags reads args against spec. A flag must be one the spec names; | |
| 39 | // a value flag consumes the next argument verbatim, "-" included; "--" | |
| 40 | // ends flag parsing. The error, when there is one, is the usage message. | |
| 41 | func parseFlags(args []string, spec flagSpec) (flags, error) { | |
| 42 | f := flags{vals: map[string]string{}, multi: map[string][]string{}, seen: map[string]bool{}} | |
| 43 | kind := map[string]byte{} | |
| 44 | for _, n := range spec.Values { | |
| 45 | kind[n] = 'v' | |
| 46 | } | |
| 47 | for _, n := range spec.Multi { | |
| 48 | kind[n] = 'm' | |
| 49 | } | |
| 50 | for _, n := range spec.Bools { | |
| 51 | kind[n] = 'b' | |
| 52 | } | |
| 53 | usage := func(format string, a ...any) error { | |
| 54 | msg := fmt.Sprintf(format, a...) | |
| 55 | if spec.Usage != "" { | |
| 56 | msg += "\nusage: " + strings.TrimPrefix(spec.Usage, "usage: ") | |
| 57 | } | |
| 58 | return fmt.Errorf("%s", msg) | |
| 59 | } | |
| 60 | onlyPos := false | |
| 61 | for i := 0; i < len(args); i++ { | |
| 62 | a := args[i] | |
| 63 | if !onlyPos && a == "--" { | |
| 64 | onlyPos = true | |
| 65 | continue | |
| 66 | } | |
| 67 | if !onlyPos && strings.HasPrefix(a, "--") { | |
| 68 | switch kind[a] { | |
| 69 | case 'b': | |
| 70 | f.seen[a] = true | |
| 71 | case 'v', 'm': | |
| 72 | if i+1 >= len(args) { | |
| 73 | return f, usage("%s requires a value", a) | |
| 74 | } | |
| 75 | f.seen[a] = true | |
| 76 | if kind[a] == 'v' { | |
| 77 | f.vals[a] = args[i+1] | |
| 78 | } else { | |
| 79 | f.multi[a] = append(f.multi[a], args[i+1]) | |
| 80 | } | |
| 81 | i++ | |
| 82 | default: | |
| 83 | return f, usage("unknown flag %q", a) | |
| 84 | } | |
| 85 | continue | |
| 86 | } | |
| 87 | if spec.MaxPos >= 0 && len(f.Pos) >= spec.MaxPos { | |
| 88 | return f, usage("unexpected argument %q", a) | |
| 89 | } | |
| 90 | f.Pos = append(f.Pos, a) | |
| 91 | } | |
| 92 | return f, nil | |
| 93 | } | |
| 94 | ||
| 95 | // pos is the nth positional argument, or "" when absent. | |
| 96 | func (f flags) pos(n int) string { | |
| 97 | if n < len(f.Pos) { | |
| 98 | return f.Pos[n] | |
| 99 | } | |
| 100 | return "" | |
| 101 | } | |
internal/control/flags_test.go added +33
| @@ -0,0 +1,33 @@ | ||
| 1 | package control | |
| 2 | ||
| 3 | import ( | |
| 4 | "strings" | |
| 5 | "testing" | |
| 6 | ) | |
| 7 | ||
| 8 | func TestParseFlags(t *testing.T) { | |
| 9 | spec := flagSpec{Values: []string{"--title", "--file"}, Multi: []string{"--add"}, Bools: []string{"--private"}, MaxPos: 1, Usage: "cmd <owner/name> [--title <t>]"} | |
| 10 | f, err := parseFlags([]string{"a/b", "--title", "-x", "--add", "one", "--private", "--add", "two", "--file", "-"}, spec) | |
| 11 | if err != nil { | |
| 12 | t.Fatal(err) | |
| 13 | } | |
| 14 | if f.pos(0) != "a/b" || f.Value("--title") != "-x" || f.Value("--file") != "-" || !f.Has("--private") || | |
| 15 | strings.Join(f.List("--add"), ",") != "one,two" || f.Has("--nope") || f.pos(1) != "" { | |
| 16 | t.Fatalf("parsed wrong: %+v", f) | |
| 17 | } | |
| 18 | for _, bad := range [][]string{ | |
| 19 | {"a/b", "--bogus"}, // unknown flag | |
| 20 | {"a/b", "--title"}, // missing value | |
| 21 | {"a/b", "c/d"}, // too many positionals | |
| 22 | {"--private", "a/b", "x"}, // too many, flags first | |
| 23 | } { | |
| 24 | if _, err := parseFlags(bad, spec); err == nil || !strings.Contains(err.Error(), "usage: cmd") { | |
| 25 | t.Errorf("%v: err=%v", bad, err) | |
| 26 | } | |
| 27 | } | |
| 28 | // "--" ends flags; MaxPos -1 takes any number. | |
| 29 | f, err = parseFlags([]string{"--", "--title", "x"}, flagSpec{Values: []string{"--title"}, MaxPos: -1}) | |
| 30 | if err != nil || strings.Join(f.Pos, " ") != "--title x" || f.Has("--title") { | |
| 31 | t.Fatalf("-- handling: %v %+v", err, f) | |
| 32 | } | |
| 33 | } | |
internal/control/ghimport.go +5 −22
| @@ -100,29 +100,12 @@ func attribution(src string, n int64, kind, login, date string) string { | ||
| 100 | 100 | } |
| 101 | 101 | |
| 102 | 102 | func runImportIssues(c *Ctx, args []string) int { |
| 103 | var path, from, apiBase string | |
| 104 | tokenStdin := false | |
| 105 | for i := 0; i < len(args); i++ { | |
| 106 | switch args[i] { | |
| 107 | case "--from", "--api-base": | |
| 108 | if i+1 >= len(args) { | |
| 109 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 110 | } | |
| 111 | if args[i] == "--from" { | |
| 112 | from = args[i+1] | |
| 113 | } else { | |
| 114 | apiBase = args[i+1] | |
| 115 | } | |
| 116 | i++ | |
| 117 | case "--token-stdin": | |
| 118 | tokenStdin = true | |
| 119 | default: | |
| 120 | if path != "" { | |
| 121 | return c.fail(protocol.ExitUsage, "unexpected argument %q", args[i]) | |
| 122 | } | |
| 123 | path = args[i] | |
| 124 | } | |
| 103 | f, err := parseFlags(args, flagSpec{Values: []string{"--from", "--api-base"}, Bools: []string{"--token-stdin"}, MaxPos: 1, | |
| 104 | Usage: "repo import-issues <owner/name> --from <owner/repo> [--api-base <url>] [--token-stdin]"}) | |
| 105 | if err != nil { | |
| 106 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 125 | 107 | } |
| 108 | path, from, apiBase, tokenStdin := f.pos(0), f.Value("--from"), f.Value("--api-base"), f.Has("--token-stdin") | |
| 126 | 109 | if path == "" || from == "" { |
| 127 | 110 | return c.fail(protocol.ExitUsage, "usage: repo import-issues <owner/name> --from <ghowner/ghrepo> [--token-stdin]") |
| 128 | 111 | } |
internal/control/identity.go +6 −11
| @@ -81,18 +81,13 @@ func runKeysList(c *Ctx, args []string) int { | ||
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | func runKeysAdd(c *Ctx, args []string) int { |
| 84 | f, err := parseFlags(args, flagSpec{Values: []string{"--scope"}, MaxPos: 0, Usage: "keys add [--scope full|git|runner] < key.pub"}) | |
| 85 | if err != nil { | |
| 86 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 87 | } | |
| 84 | 88 | scope := "full" |
| 85 | for i := 0; i < len(args); i++ { | |
| 86 | switch args[i] { | |
| 87 | case "--scope": | |
| 88 | if i+1 >= len(args) { | |
| 89 | return c.fail(protocol.ExitUsage, "--scope requires a value") | |
| 90 | } | |
| 91 | scope = args[i+1] | |
| 92 | i++ | |
| 93 | default: | |
| 94 | return c.fail(protocol.ExitUsage, "usage: keys add [--scope full|git|runner] < key.pub") | |
| 95 | } | |
| 89 | if f.Has("--scope") { | |
| 90 | scope = f.Value("--scope") | |
| 96 | 91 | } |
| 97 | 92 | if scope != "full" && scope != "git" && scope != "runner" { |
| 98 | 93 | // deploy:* scopes are granted via repo settings, not self-service. |
internal/control/import.go +5 −21
| @@ -33,28 +33,12 @@ esac | ||
| 33 | 33 | ` |
| 34 | 34 | |
| 35 | 35 | func runRepoImport(c *Ctx, args []string) int { |
| 36 | var path, from string | |
| 37 | private := false | |
| 38 | tokenStdin := false | |
| 39 | for i := 0; i < len(args); i++ { | |
| 40 | switch args[i] { | |
| 41 | case "--from": | |
| 42 | if i+1 >= len(args) { | |
| 43 | return c.fail(protocol.ExitUsage, "--from requires a URL") | |
| 44 | } | |
| 45 | from = args[i+1] | |
| 46 | i++ | |
| 47 | case "--private": | |
| 48 | private = true | |
| 49 | case "--token-stdin": | |
| 50 | tokenStdin = true | |
| 51 | default: | |
| 52 | if path != "" { | |
| 53 | return c.fail(protocol.ExitUsage, "unexpected argument %q", args[i]) | |
| 54 | } | |
| 55 | path = args[i] | |
| 56 | } | |
| 36 | f, err := parseFlags(args, flagSpec{Values: []string{"--from"}, Bools: []string{"--private", "--token-stdin"}, MaxPos: 1, | |
| 37 | Usage: "repo import <owner/name> --from <url> [--private] [--token-stdin]"}) | |
| 38 | if err != nil { | |
| 39 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 57 | 40 | } |
| 41 | path, from, private, tokenStdin := f.pos(0), f.Value("--from"), f.Has("--private"), f.Has("--token-stdin") | |
| 58 | 42 | if path == "" || from == "" { |
| 59 | 43 | return c.fail(protocol.ExitUsage, "usage: repo import <owner/name> --from <url> [--private] [--token-stdin]") |
| 60 | 44 | } |
internal/control/issue.go +30 −123
| @@ -123,40 +123,12 @@ func issueToOut(i store.Issue, withBody bool) issueOut { | ||
| 123 | 123 | } |
| 124 | 124 | |
| 125 | 125 | func runIssueCreate(c *Ctx, args []string) int { |
| 126 | var path, title, body, file, format string | |
| 127 | for i := 0; i < len(args); i++ { | |
| 128 | switch args[i] { | |
| 129 | case "--format": | |
| 130 | if i+1 >= len(args) { | |
| 131 | return c.fail(protocol.ExitUsage, "--format requires a value") | |
| 132 | } | |
| 133 | format = args[i+1] | |
| 134 | i++ | |
| 135 | case "--title": | |
| 136 | if i+1 >= len(args) { | |
| 137 | return c.fail(protocol.ExitUsage, "--title requires a value") | |
| 138 | } | |
| 139 | title = args[i+1] | |
| 140 | i++ | |
| 141 | case "--body": | |
| 142 | if i+1 >= len(args) { | |
| 143 | return c.fail(protocol.ExitUsage, "--body requires a value") | |
| 144 | } | |
| 145 | body = args[i+1] | |
| 146 | i++ | |
| 147 | case "--file": | |
| 148 | if i+1 >= len(args) { | |
| 149 | return c.fail(protocol.ExitUsage, "--file requires a value") | |
| 150 | } | |
| 151 | file = args[i+1] | |
| 152 | i++ | |
| 153 | default: | |
| 154 | if path != "" { | |
| 155 | return c.fail(protocol.ExitUsage, "unexpected argument %q", args[i]) | |
| 156 | } | |
| 157 | path = args[i] | |
| 158 | } | |
| 126 | f, err := parseFlags(args, flagSpec{Values: []string{"--format", "--title", "--body", "--file"}, MaxPos: 1, | |
| 127 | Usage: "issue create <owner/name> --title <t> [--body <b> | --file -] [--format md|org]"}) | |
| 128 | if err != nil { | |
| 129 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 159 | 130 | } |
| 131 | path, title, body, file, format := f.pos(0), f.Value("--title"), f.Value("--body"), f.Value("--file"), f.Value("--format") | |
| 160 | 132 | if path == "" || title == "" { |
| 161 | 133 | return c.fail(protocol.ExitUsage, "usage: issue create <owner/name> --title <t> [--body <b> | --file -] [--format md|org]") |
| 162 | 134 | } |
| @@ -200,33 +172,15 @@ func runIssueList(c *Ctx, args []string) int { | ||
| 200 | 172 | } |
| 201 | 173 | const usage = "usage: issue list <owner/name> [--state open|closed|all] [--label <l>] [--assignee <user>] [--author <user>] [--milestone <title>|none] [--limit <n>] [--cursor <c>]" |
| 202 | 174 | f := store.IssueFilter{State: "open"} |
| 203 | var path string | |
| 204 | for i := 0; i < len(args); i++ { | |
| 205 | var target *string | |
| 206 | switch args[i] { | |
| 207 | case "--state": | |
| 208 | target = &f.State | |
| 209 | case "--label": | |
| 210 | target = &f.Label | |
| 211 | case "--assignee": | |
| 212 | target = &f.Assignee | |
| 213 | case "--author": | |
| 214 | target = &f.Author | |
| 215 | case "--milestone": | |
| 216 | target = &f.Milestone | |
| 217 | default: | |
| 218 | if path != "" || strings.HasPrefix(args[i], "--") { | |
| 219 | return c.fail(protocol.ExitUsage, "unexpected argument %q\n%s", args[i], usage) | |
| 220 | } | |
| 221 | path = args[i] | |
| 222 | continue | |
| 223 | } | |
| 224 | if i+1 >= len(args) { | |
| 225 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 226 | } | |
| 227 | *target = args[i+1] | |
| 228 | i++ | |
| 175 | fl, err := parseFlags(args, flagSpec{Values: []string{"--state", "--label", "--assignee", "--author", "--milestone"}, MaxPos: 1, Usage: usage}) | |
| 176 | if err != nil { | |
| 177 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 178 | } | |
| 179 | path := fl.pos(0) | |
| 180 | if fl.Has("--state") { | |
| 181 | f.State = fl.Value("--state") | |
| 229 | 182 | } |
| 183 | f.Label, f.Assignee, f.Author, f.Milestone = fl.Value("--label"), fl.Value("--assignee"), fl.Value("--author"), fl.Value("--milestone") | |
| 230 | 184 | if path == "" || (f.State != "open" && f.State != "closed" && f.State != "all") { |
| 231 | 185 | return c.fail(protocol.ExitUsage, usage) |
| 232 | 186 | } |
| @@ -298,32 +252,13 @@ func runIssueShow(c *Ctx, args []string) int { | ||
| 298 | 252 | } |
| 299 | 253 | |
| 300 | 254 | func runIssueComment(c *Ctx, args []string) int { |
| 301 | var rest []string | |
| 302 | var message, file, format string | |
| 303 | for i := 0; i < len(args); i++ { | |
| 304 | switch args[i] { | |
| 305 | case "--format": | |
| 306 | if i+1 >= len(args) { | |
| 307 | return c.fail(protocol.ExitUsage, "--format requires a value") | |
| 308 | } | |
| 309 | format = args[i+1] | |
| 310 | i++ | |
| 311 | case "--message": | |
| 312 | if i+1 >= len(args) { | |
| 313 | return c.fail(protocol.ExitUsage, "--message requires a value") | |
| 314 | } | |
| 315 | message = args[i+1] | |
| 316 | i++ | |
| 317 | case "--file": | |
| 318 | if i+1 >= len(args) { | |
| 319 | return c.fail(protocol.ExitUsage, "--file requires a value") | |
| 320 | } | |
| 321 | file = args[i+1] | |
| 322 | i++ | |
| 323 | default: | |
| 324 | rest = append(rest, args[i]) | |
| 325 | } | |
| 255 | f, err := parseFlags(args, flagSpec{Values: []string{"--format", "--message", "--file"}, MaxPos: -1, | |
| 256 | Usage: "issue comment <owner/name> <n> [--message <m> | --file -] [--format md|org]"}) | |
| 257 | if err != nil { | |
| 258 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 326 | 259 | } |
| 260 | rest := f.Pos | |
| 261 | message, file, format := f.Value("--message"), f.Value("--file"), f.Value("--format") | |
| 327 | 262 | fmtName, err := markupFormat(format) |
| 328 | 263 | if err != nil { |
| 329 | 264 | return c.failErr(err) |
| @@ -398,29 +333,14 @@ func setIssueState(c *Ctx, args []string, state string) int { | ||
| 398 | 333 | // editText parses --title/--body/--file -/--format and authorizes: author or |
| 399 | 334 | // write. A nil format means the stored markup format stays as it is. |
| 400 | 335 | func editText(c *Ctx, args []string, kind string) (rest []string, title, body, format *string, code int) { |
| 401 | var titleV, bodyV, file, formatV string | |
| 402 | haveTitle, haveBody := false, false | |
| 403 | for i := 0; i < len(args); i++ { | |
| 404 | switch args[i] { | |
| 405 | case "--title", "--body", "--file", "--format": | |
| 406 | if i+1 >= len(args) { | |
| 407 | return nil, nil, nil, nil, c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 408 | } | |
| 409 | switch args[i] { | |
| 410 | case "--title": | |
| 411 | titleV, haveTitle = args[i+1], true | |
| 412 | case "--body": | |
| 413 | bodyV, haveBody = args[i+1], true | |
| 414 | case "--file": | |
| 415 | file = args[i+1] | |
| 416 | case "--format": | |
| 417 | formatV = args[i+1] | |
| 418 | } | |
| 419 | i++ | |
| 420 | default: | |
| 421 | rest = append(rest, args[i]) | |
| 422 | } | |
| 336 | f, err := parseFlags(args, flagSpec{Values: []string{"--title", "--body", "--file", "--format"}, MaxPos: -1, | |
| 337 | Usage: kind + " edit <owner/name> <n> [--title <t>] [--body <b> | --file -] [--format md|org]"}) | |
| 338 | if err != nil { | |
| 339 | return nil, nil, nil, nil, c.fail(protocol.ExitUsage, "%v", err) | |
| 423 | 340 | } |
| 341 | rest = f.Pos | |
| 342 | titleV, bodyV, file, formatV := f.Value("--title"), f.Value("--body"), f.Value("--file"), f.Value("--format") | |
| 343 | haveTitle, haveBody := f.Has("--title"), f.Has("--body") | |
| 424 | 344 | if file != "" { |
| 425 | 345 | b, err := bodyFrom(c, "", file) |
| 426 | 346 | if err != nil { |
| @@ -482,24 +402,11 @@ func runIssueReopen(c *Ctx, args []string) int { return setIssueState(c, args, " | ||
| 482 | 402 | |
| 483 | 403 | // addRemoveFlags parses repeated --add/--remove flags. |
| 484 | 404 | func addRemoveFlags(args []string) (rest, adds, removes []string, err error) { |
| 485 | for i := 0; i < len(args); i++ { | |
| 486 | switch args[i] { | |
| 487 | case "--add": | |
| 488 | if i+1 >= len(args) { | |
| 489 | return nil, nil, nil, errors.New("--add requires a value") | |
| 490 | } | |
| 491 | adds = append(adds, args[i+1]) | |
| 492 | i++ | |
| 493 | case "--remove": | |
| 494 | if i+1 >= len(args) { | |
| 495 | return nil, nil, nil, errors.New("--remove requires a value") | |
| 496 | } | |
| 497 | removes = append(removes, args[i+1]) | |
| 498 | i++ | |
| 499 | default: | |
| 500 | rest = append(rest, args[i]) | |
| 501 | } | |
| 405 | f, err := parseFlags(args, flagSpec{Multi: []string{"--add", "--remove"}, MaxPos: -1}) | |
| 406 | if err != nil { | |
| 407 | return nil, nil, nil, err | |
| 502 | 408 | } |
| 409 | rest, adds, removes = f.Pos, f.List("--add"), f.List("--remove") | |
| 503 | 410 | return rest, adds, removes, nil |
| 504 | 411 | } |
| 505 | 412 | |
internal/control/label.go +5 −12
| @@ -50,19 +50,12 @@ func runLabelList(c *Ctx, args []string) int { | ||
| 50 | 50 | |
| 51 | 51 | func runLabelSet(c *Ctx, args []string) int { |
| 52 | 52 | const usage = "usage: label set <owner/name> <label> [--color #rrggbb|'']" |
| 53 | var rest []string | |
| 54 | color, colorSet := "", false | |
| 55 | for i := 0; i < len(args); i++ { | |
| 56 | if args[i] == "--color" { | |
| 57 | if i+1 >= len(args) { | |
| 58 | return c.fail(protocol.ExitUsage, "--color requires rrggbb, or '' to clear") | |
| 59 | } | |
| 60 | color, colorSet = strings.ToLower(args[i+1]), true | |
| 61 | i++ | |
| 62 | continue | |
| 63 | } | |
| 64 | rest = append(rest, args[i]) | |
| 53 | f, err := parseFlags(args, flagSpec{Values: []string{"--color"}, MaxPos: -1, Usage: usage}) | |
| 54 | if err != nil { | |
| 55 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 65 | 56 | } |
| 57 | rest := f.Pos | |
| 58 | color, colorSet := strings.ToLower(f.Value("--color")), f.Has("--color") | |
| 66 | 59 | if len(rest) != 2 { |
| 67 | 60 | return c.fail(protocol.ExitUsage, usage) |
| 68 | 61 | } |
internal/control/migrate.go +4 −8
| @@ -139,15 +139,11 @@ func migAttribution(src, kind, author, date string, n int64) string { | ||
| 139 | 139 | // the git push that follows cannot be refused by them. Resumable: markers |
| 140 | 140 | // skip everything already imported. |
| 141 | 141 | func runAccountImportBundle(c *Ctx, args []string) int { |
| 142 | var src string | |
| 143 | for i := 0; i < len(args); i++ { | |
| 144 | if args[i] == "--source" && i+1 < len(args) { | |
| 145 | src = args[i+1] | |
| 146 | i++ | |
| 147 | } else { | |
| 148 | return c.fail(protocol.ExitUsage, "usage: account import-bundle [--source <host>] < bundle.json") | |
| 149 | } | |
| 142 | f, err := parseFlags(args, flagSpec{Values: []string{"--source"}, MaxPos: 0, Usage: "account import-bundle [--source <host>] < bundle.json"}) | |
| 143 | if err != nil { | |
| 144 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 150 | 145 | } |
| 146 | src := f.Value("--source") | |
| 151 | 147 | if src == "" { |
| 152 | 148 | src = "the previous instance" |
| 153 | 149 | } |
internal/control/milestone.go +12 −36
| @@ -41,29 +41,12 @@ func init() { | ||
| 41 | 41 | var duePat = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) |
| 42 | 42 | |
| 43 | 43 | func runMilestoneCreate(c *Ctx, args []string) int { |
| 44 | var path, title, description, due string | |
| 45 | for i := 0; i < len(args); i++ { | |
| 46 | switch args[i] { | |
| 47 | case "--description", "--due": | |
| 48 | if i+1 >= len(args) { | |
| 49 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 50 | } | |
| 51 | if args[i] == "--description" { | |
| 52 | description = args[i+1] | |
| 53 | } else { | |
| 54 | due = args[i+1] | |
| 55 | } | |
| 56 | i++ | |
| 57 | default: | |
| 58 | if path == "" { | |
| 59 | path = args[i] | |
| 60 | } else if title == "" { | |
| 61 | title = args[i] | |
| 62 | } else { | |
| 63 | return c.fail(protocol.ExitUsage, "usage: milestone create <owner/name> <title> [--description <d>] [--due YYYY-MM-DD]") | |
| 64 | } | |
| 65 | } | |
| 44 | f, err := parseFlags(args, flagSpec{Values: []string{"--description", "--due"}, MaxPos: 2, | |
| 45 | Usage: "milestone create <owner/name> <title> [--description <d>] [--due YYYY-MM-DD]"}) | |
| 46 | if err != nil { | |
| 47 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 66 | 48 | } |
| 49 | path, title, description, due := f.pos(0), f.pos(1), f.Value("--description"), f.Value("--due") | |
| 67 | 50 | if path == "" || title == "" { |
| 68 | 51 | return c.fail(protocol.ExitUsage, "usage: milestone create <owner/name> <title> [--description <d>] [--due YYYY-MM-DD]") |
| 69 | 52 | } |
| @@ -86,20 +69,13 @@ func runMilestoneCreate(c *Ctx, args []string) int { | ||
| 86 | 69 | } |
| 87 | 70 | |
| 88 | 71 | func runMilestoneList(c *Ctx, args []string) int { |
| 89 | state := "open" | |
| 90 | var path string | |
| 91 | for i := 0; i < len(args); i++ { | |
| 92 | if args[i] == "--state" { | |
| 93 | if i+1 >= len(args) { | |
| 94 | return c.fail(protocol.ExitUsage, "--state requires open|closed|all") | |
| 95 | } | |
| 96 | state = args[i+1] | |
| 97 | i++ | |
| 98 | } else if path == "" { | |
| 99 | path = args[i] | |
| 100 | } else { | |
| 101 | return c.fail(protocol.ExitUsage, "usage: milestone list <owner/name> [--state open|closed|all]") | |
| 102 | } | |
| 72 | f, err := parseFlags(args, flagSpec{Values: []string{"--state"}, MaxPos: 1, Usage: "milestone list <owner/name> [--state open|closed|all]"}) | |
| 73 | if err != nil { | |
| 74 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 75 | } | |
| 76 | state, path := "open", f.pos(0) | |
| 77 | if f.Has("--state") { | |
| 78 | state = f.Value("--state") | |
| 103 | 79 | } |
| 104 | 80 | if path == "" || (state != "open" && state != "closed" && state != "all") { |
| 105 | 81 | return c.fail(protocol.ExitUsage, "usage: milestone list <owner/name> [--state open|closed|all]") |
internal/control/mirrorcmd.go +6 −25
| @@ -31,32 +31,13 @@ func init() { | ||
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | func runMirrorAdd(c *Ctx, args []string) int { |
| 34 | var path, urlArg, direction, username string | |
| 35 | tokenStdin := false | |
| 36 | for i := 0; i < len(args); i++ { | |
| 37 | switch args[i] { | |
| 38 | case "--direction", "--username": | |
| 39 | if i+1 >= len(args) { | |
| 40 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 41 | } | |
| 42 | if args[i] == "--direction" { | |
| 43 | direction = args[i+1] | |
| 44 | } else { | |
| 45 | username = args[i+1] | |
| 46 | } | |
| 47 | i++ | |
| 48 | case "--token-stdin": | |
| 49 | tokenStdin = true | |
| 50 | default: | |
| 51 | if path == "" { | |
| 52 | path = args[i] | |
| 53 | } else if urlArg == "" { | |
| 54 | urlArg = args[i] | |
| 55 | } else { | |
| 56 | return c.fail(protocol.ExitUsage, "unexpected argument %q", args[i]) | |
| 57 | } | |
| 58 | } | |
| 34 | f, err := parseFlags(args, flagSpec{Values: []string{"--direction", "--username"}, Bools: []string{"--token-stdin"}, MaxPos: 2, | |
| 35 | Usage: "repo mirror add <owner/name> <url> --direction push|pull [--username <u>] [--token-stdin]"}) | |
| 36 | if err != nil { | |
| 37 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 59 | 38 | } |
| 39 | path, urlArg := f.pos(0), f.pos(1) | |
| 40 | direction, username, tokenStdin := f.Value("--direction"), f.Value("--username"), f.Has("--token-stdin") | |
| 60 | 41 | if path == "" || urlArg == "" || (direction != "push" && direction != "pull") { |
| 61 | 42 | return c.fail(protocol.ExitUsage, "usage: repo mirror add <owner/name> <https-url> --direction push|pull [--username <u>] [--token-stdin]") |
| 62 | 43 | } |
internal/control/mr.go +28 −98
| @@ -68,22 +68,11 @@ func init() { | ||
| 68 | 68 | } |
| 69 | 69 | |
| 70 | 70 | func runRepoFork(c *Ctx, args []string) int { |
| 71 | var path, name string | |
| 72 | for i := 0; i < len(args); i++ { | |
| 73 | switch args[i] { | |
| 74 | case "--name": | |
| 75 | if i+1 >= len(args) { | |
| 76 | return c.fail(protocol.ExitUsage, "--name requires a value") | |
| 77 | } | |
| 78 | name = args[i+1] | |
| 79 | i++ | |
| 80 | default: | |
| 81 | if path != "" { | |
| 82 | return c.fail(protocol.ExitUsage, "usage: repo fork <owner/name> [--name <n>]") | |
| 83 | } | |
| 84 | path = args[i] | |
| 85 | } | |
| 71 | f, err := parseFlags(args, flagSpec{Values: []string{"--name"}, MaxPos: 1, Usage: "repo fork <owner/name> [--name <n>]"}) | |
| 72 | if err != nil { | |
| 73 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 86 | 74 | } |
| 75 | path, name := f.pos(0), f.Value("--name") | |
| 87 | 76 | if path == "" { |
| 88 | 77 | return c.fail(protocol.ExitUsage, "usage: repo fork <owner/name> [--name <n>]") |
| 89 | 78 | } |
| @@ -231,36 +220,13 @@ func mrRef(c *Ctx, args []string, perm func(store.User, store.Repo, string) bool | ||
| 231 | 220 | func mrHeadRef(n int64) string { return fmt.Sprintf("refs/merge-requests/%d/head", n) } |
| 232 | 221 | |
| 233 | 222 | func runMRCreate(c *Ctx, args []string) int { |
| 234 | var path, source, target, title, body, file, format string | |
| 235 | for i := 0; i < len(args); i++ { | |
| 236 | switch args[i] { | |
| 237 | case "--source", "--target", "--title", "--body", "--file", "--format": | |
| 238 | if i+1 >= len(args) { | |
| 239 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 240 | } | |
| 241 | v := args[i+1] | |
| 242 | switch args[i] { | |
| 243 | case "--source": | |
| 244 | source = v | |
| 245 | case "--target": | |
| 246 | target = v | |
| 247 | case "--title": | |
| 248 | title = v | |
| 249 | case "--body": | |
| 250 | body = v | |
| 251 | case "--file": | |
| 252 | file = v | |
| 253 | case "--format": | |
| 254 | format = v | |
| 255 | } | |
| 256 | i++ | |
| 257 | default: | |
| 258 | if path != "" { | |
| 259 | return c.fail(protocol.ExitUsage, "unexpected argument %q", args[i]) | |
| 260 | } | |
| 261 | path = args[i] | |
| 262 | } | |
| 223 | f, err := parseFlags(args, flagSpec{Values: []string{"--source", "--target", "--title", "--body", "--file", "--format"}, MaxPos: 1, | |
| 224 | Usage: "mr create <target owner/name> --source [owner/name:]<branch> --target <branch> --title <t>"}) | |
| 225 | if err != nil { | |
| 226 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 263 | 227 | } |
| 228 | path, source, target := f.pos(0), f.Value("--source"), f.Value("--target") | |
| 229 | title, body, file, format := f.Value("--title"), f.Value("--body"), f.Value("--file"), f.Value("--format") | |
| 264 | 230 | if path == "" || source == "" || title == "" { |
| 265 | 231 | return c.fail(protocol.ExitUsage, "usage: mr create <target owner/name> --source [owner/name:]<branch> --target <branch> --title <t>") |
| 266 | 232 | } |
| @@ -415,29 +381,15 @@ func runMRList(c *Ctx, args []string) int { | ||
| 415 | 381 | } |
| 416 | 382 | const usage = "usage: mr list <owner/name> [--state open|merged|closed|source_gone|all] [--author <user>] [--milestone <title>|none] [--limit <n>] [--cursor <c>]" |
| 417 | 383 | f := store.MRFilter{State: "open"} |
| 418 | var path string | |
| 419 | for i := 0; i < len(args); i++ { | |
| 420 | var target *string | |
| 421 | switch args[i] { | |
| 422 | case "--state": | |
| 423 | target = &f.State | |
| 424 | case "--author": | |
| 425 | target = &f.Author | |
| 426 | case "--milestone": | |
| 427 | target = &f.Milestone | |
| 428 | default: | |
| 429 | if path != "" || strings.HasPrefix(args[i], "--") { | |
| 430 | return c.fail(protocol.ExitUsage, "unexpected argument %q\n%s", args[i], usage) | |
| 431 | } | |
| 432 | path = args[i] | |
| 433 | continue | |
| 434 | } | |
| 435 | if i+1 >= len(args) { | |
| 436 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 437 | } | |
| 438 | *target = args[i+1] | |
| 439 | i++ | |
| 384 | fl, err := parseFlags(args, flagSpec{Values: []string{"--state", "--author", "--milestone"}, MaxPos: 1, Usage: usage}) | |
| 385 | if err != nil { | |
| 386 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 440 | 387 | } |
| 388 | path := fl.pos(0) | |
| 389 | if fl.Has("--state") { | |
| 390 | f.State = fl.Value("--state") | |
| 391 | } | |
| 392 | f.Author, f.Milestone = fl.Value("--author"), fl.Value("--milestone") | |
| 441 | 393 | valid := map[string]bool{"open": true, "merged": true, "closed": true, "source_gone": true, "all": true} |
| 442 | 394 | if path == "" || !valid[f.State] { |
| 443 | 395 | return c.fail(protocol.ExitUsage, usage) |
| @@ -728,27 +680,13 @@ func runMRRetarget(c *Ctx, args []string) int { | ||
| 728 | 680 | } |
| 729 | 681 | |
| 730 | 682 | func runMRComment(c *Ctx, args []string) int { |
| 731 | var rest []string | |
| 732 | var message, file, format string | |
| 733 | for i := 0; i < len(args); i++ { | |
| 734 | switch args[i] { | |
| 735 | case "--message", "--file", "--format": | |
| 736 | if i+1 >= len(args) { | |
| 737 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 738 | } | |
| 739 | switch args[i] { | |
| 740 | case "--message": | |
| 741 | message = args[i+1] | |
| 742 | case "--file": | |
| 743 | file = args[i+1] | |
| 744 | case "--format": | |
| 745 | format = args[i+1] | |
| 746 | } | |
| 747 | i++ | |
| 748 | default: | |
| 749 | rest = append(rest, args[i]) | |
| 750 | } | |
| 683 | f, err := parseFlags(args, flagSpec{Values: []string{"--message", "--file", "--format"}, MaxPos: -1, | |
| 684 | Usage: "mr comment <owner/name> <n> [--message <m> | --file -] [--format md|org]"}) | |
| 685 | if err != nil { | |
| 686 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 751 | 687 | } |
| 688 | rest := f.Pos | |
| 689 | message, file, format := f.Value("--message"), f.Value("--file"), f.Value("--format") | |
| 752 | 690 | fmtName, err := markupFormat(format) |
| 753 | 691 | if err != nil { |
| 754 | 692 | return c.failErr(err) |
| @@ -824,19 +762,11 @@ func runMRReview(c *Ctx, args []string) int { | ||
| 824 | 762 | } |
| 825 | 763 | |
| 826 | 764 | func runMRMerge(c *Ctx, args []string) int { |
| 827 | strategy := "" | |
| 828 | var rest []string | |
| 829 | for i := 0; i < len(args); i++ { | |
| 830 | if args[i] == "--strategy" { | |
| 831 | if i+1 >= len(args) { | |
| 832 | return c.fail(protocol.ExitUsage, "--strategy requires ff|merge|squash|rebase") | |
| 833 | } | |
| 834 | strategy = args[i+1] | |
| 835 | i++ | |
| 836 | continue | |
| 837 | } | |
| 838 | rest = append(rest, args[i]) | |
| 765 | f, err := parseFlags(args, flagSpec{Values: []string{"--strategy"}, MaxPos: -1, Usage: "mr merge <owner/name> <n> [--strategy ff|merge|squash|rebase]"}) | |
| 766 | if err != nil { | |
| 767 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 839 | 768 | } |
| 769 | strategy, rest := f.Value("--strategy"), f.Pos | |
| 840 | 770 | valid := map[string]bool{"": true, "ff": true, "merge": true, "squash": true, "rebase": true} |
| 841 | 771 | if !valid[strategy] { |
| 842 | 772 | return c.fail(protocol.ExitUsage, "--strategy must be ff, merge, squash, or rebase") |
internal/control/org.go +7 −12
| @@ -192,18 +192,13 @@ func runOrgDelete(c *Ctx, args []string) int { | ||
| 192 | 192 | } |
| 193 | 193 | |
| 194 | 194 | func runOrgMembersAdd(c *Ctx, args []string) int { |
| 195 | role := "member" | |
| 196 | var rest []string | |
| 197 | for i := 0; i < len(args); i++ { | |
| 198 | if args[i] == "--role" { | |
| 199 | if i+1 >= len(args) { | |
| 200 | return c.fail(protocol.ExitUsage, "--role requires member|admin") | |
| 201 | } | |
| 202 | role = args[i+1] | |
| 203 | i++ | |
| 204 | continue | |
| 205 | } | |
| 206 | rest = append(rest, args[i]) | |
| 195 | f, err := parseFlags(args, flagSpec{Values: []string{"--role"}, MaxPos: -1, Usage: "org members add <org> <user> [--role member|admin]"}) | |
| 196 | if err != nil { | |
| 197 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 198 | } | |
| 199 | role, rest := "member", f.Pos | |
| 200 | if f.Has("--role") { | |
| 201 | role = f.Value("--role") | |
| 207 | 202 | } |
| 208 | 203 | if len(rest) != 2 || (role != "member" && role != "admin") { |
| 209 | 204 | return c.fail(protocol.ExitUsage, "usage: org members add <org> <user> [--role member|admin]") |
internal/control/profile.go +36 −34
| @@ -52,41 +52,43 @@ func parseProfileFlags(c *Ctx, args []string) (rest []string, e profileEdit, err | ||
| 52 | 52 | about, file := "", "" |
| 53 | 53 | sawAbout := false |
| 54 | 54 | var links []store.ProfileLink |
| 55 | for i := 0; i < len(args); i++ { | |
| 56 | switch args[i] { | |
| 57 | case "--description", "--website", "--about", "--about-format", "--file", "--link": | |
| 58 | if i+1 >= len(args) { | |
| 59 | return nil, e, fmt.Errorf("%s requires a value", args[i]) | |
| 60 | } | |
| 61 | v := args[i+1] | |
| 62 | switch args[i] { | |
| 63 | case "--description": | |
| 64 | e.Description = &v | |
| 65 | case "--website": | |
| 66 | e.Website = &v | |
| 67 | case "--about": | |
| 68 | about, sawAbout = v, true | |
| 69 | case "--about-format": | |
| 70 | e.AboutFormat = &v | |
| 71 | case "--file": | |
| 72 | file, sawAbout = v, true | |
| 73 | case "--link": | |
| 74 | if v == "" { | |
| 75 | links = nil | |
| 76 | e.Links = &links | |
| 77 | break | |
| 78 | } | |
| 79 | l, lerr := parseProfileLink(v) | |
| 80 | if lerr != nil { | |
| 81 | return nil, e, lerr | |
| 82 | } | |
| 83 | links = append(links, l) | |
| 84 | e.Links = &links | |
| 85 | } | |
| 86 | i++ | |
| 87 | default: | |
| 88 | rest = append(rest, args[i]) | |
| 55 | f, err := parseFlags(args, flagSpec{Values: []string{"--description", "--website", "--about", "--about-format", "--file"}, Multi: []string{"--link"}, MaxPos: -1}) | |
| 56 | if err != nil { | |
| 57 | return nil, e, err | |
| 58 | } | |
| 59 | rest = f.Pos | |
| 60 | for _, name := range []string{"--description", "--website", "--about-format"} { | |
| 61 | if !f.Has(name) { | |
| 62 | continue | |
| 63 | } | |
| 64 | v := f.Value(name) | |
| 65 | switch name { | |
| 66 | case "--description": | |
| 67 | e.Description = &v | |
| 68 | case "--website": | |
| 69 | e.Website = &v | |
| 70 | case "--about-format": | |
| 71 | e.AboutFormat = &v | |
| 72 | } | |
| 73 | } | |
| 74 | if f.Has("--about") { | |
| 75 | about, sawAbout = f.Value("--about"), true | |
| 76 | } | |
| 77 | if f.Has("--file") { | |
| 78 | file, sawAbout = f.Value("--file"), true | |
| 79 | } | |
| 80 | for _, v := range f.List("--link") { | |
| 81 | if v == "" { | |
| 82 | links = nil | |
| 83 | e.Links = &links | |
| 84 | continue | |
| 85 | } | |
| 86 | l, lerr := parseProfileLink(v) | |
| 87 | if lerr != nil { | |
| 88 | return nil, e, lerr | |
| 89 | 89 | } |
| 90 | links = append(links, l) | |
| 91 | e.Links = &links | |
| 90 | 92 | } |
| 91 | 93 | if sawAbout { |
| 92 | 94 | body, berr := bodyFrom(c, about, file) |
internal/control/read.go +17 −45
| @@ -94,36 +94,21 @@ const BlameSpan = 1000 | ||
| 94 | 94 | |
| 95 | 95 | func runRepoBlame(c *Ctx, args []string) int { |
| 96 | 96 | const usage = "repo blame <owner/name> <path> [--ref <ref>] [--from <n>] [--to <n>]" |
| 97 | var rest []string | |
| 98 | var ref string | |
| 97 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref", "--from", "--to"}, MaxPos: -1, Usage: usage}) | |
| 98 | if err != nil { | |
| 99 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 100 | } | |
| 101 | rest, ref := f.Pos, f.Value("--ref") | |
| 99 | 102 | from, to := 0, 0 |
| 100 | for i := 0; i < len(args); i++ { | |
| 101 | switch args[i] { | |
| 102 | case "--ref", "--from", "--to": | |
| 103 | if i+1 >= len(args) { | |
| 104 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 105 | } | |
| 106 | v := args[i+1] | |
| 107 | if args[i] == "--ref" { | |
| 108 | ref = v | |
| 109 | } else { | |
| 110 | n, err := strconv.Atoi(v) | |
| 111 | if err != nil || n < 1 { | |
| 112 | return c.fail(protocol.ExitUsage, "%s must be a positive line number", args[i]) | |
| 113 | } | |
| 114 | if args[i] == "--from" { | |
| 115 | from = n | |
| 116 | } else { | |
| 117 | to = n | |
| 118 | } | |
| 119 | } | |
| 120 | i++ | |
| 121 | default: | |
| 122 | if strings.HasPrefix(args[i], "--") { | |
| 123 | return c.fail(protocol.ExitUsage, "unknown flag %q\nusage: %s", args[i], usage) | |
| 124 | } | |
| 125 | rest = append(rest, args[i]) | |
| 103 | for name, dst := range map[string]*int{"--from": &from, "--to": &to} { | |
| 104 | if !f.Has(name) { | |
| 105 | continue | |
| 106 | } | |
| 107 | n, err := strconv.Atoi(f.Value(name)) | |
| 108 | if err != nil || n < 1 { | |
| 109 | return c.fail(protocol.ExitUsage, "%s must be a positive line number", name) | |
| 126 | 110 | } |
| 111 | *dst = n | |
| 127 | 112 | } |
| 128 | 113 | if len(rest) != 2 { |
| 129 | 114 | return c.fail(protocol.ExitUsage, "usage: %s", usage) |
| @@ -219,24 +204,11 @@ func runRepoBlame(c *Ctx, args []string) int { | ||
| 219 | 204 | // off argv. Positionals are returned in order so each command can name them |
| 220 | 205 | // in its own usage message. |
| 221 | 206 | func readArgs(c *Ctx, args []string, usage string, maxPos int) (pos []string, ref string, code int) { |
| 222 | for i := 0; i < len(args); i++ { | |
| 223 | switch args[i] { | |
| 224 | case "--ref": | |
| 225 | if i+1 >= len(args) { | |
| 226 | return nil, "", c.fail(protocol.ExitUsage, "--ref requires a value") | |
| 227 | } | |
| 228 | ref = args[i+1] | |
| 229 | i++ | |
| 230 | default: | |
| 231 | if strings.HasPrefix(args[i], "--") { | |
| 232 | return nil, "", c.fail(protocol.ExitUsage, "unknown flag %q\nusage: %s", args[i], usage) | |
| 233 | } | |
| 234 | if len(pos) >= maxPos { | |
| 235 | return nil, "", c.fail(protocol.ExitUsage, "usage: %s", usage) | |
| 236 | } | |
| 237 | pos = append(pos, args[i]) | |
| 238 | } | |
| 207 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref"}, MaxPos: maxPos, Usage: usage}) | |
| 208 | if err != nil { | |
| 209 | return nil, "", c.fail(protocol.ExitUsage, "%v", err) | |
| 239 | 210 | } |
| 211 | pos, ref = f.Pos, f.Value("--ref") | |
| 240 | 212 | return pos, ref, -1 |
| 241 | 213 | } |
| 242 | 214 | |
internal/control/register.go +6 −22
| @@ -120,29 +120,13 @@ func runEmailVerify(c *Ctx, args []string) int { | ||
| 120 | 120 | // that registration is enabled and that argv[0] == "register". |
| 121 | 121 | func RunRegister(cfg config.Config, st *store.Store, pub ssh.PublicKey, argv []string, |
| 122 | 122 | stdout, stderr io.Writer) int { |
| 123 | var username, email, invite string | |
| 124 | args := argv[1:] | |
| 125 | for i := 0; i < len(args); i++ { | |
| 126 | switch args[i] { | |
| 127 | case "--username", "--email", "--invite": | |
| 128 | if i+1 >= len(args) { | |
| 129 | fmt.Fprintf(stderr, "%s requires a value\n", args[i]) | |
| 130 | return protocol.ExitUsage | |
| 131 | } | |
| 132 | switch args[i] { | |
| 133 | case "--username": | |
| 134 | username = args[i+1] | |
| 135 | case "--email": | |
| 136 | email = args[i+1] | |
| 137 | case "--invite": | |
| 138 | invite = args[i+1] | |
| 139 | } | |
| 140 | i++ | |
| 141 | default: | |
| 142 | fmt.Fprintf(stderr, "unexpected argument %q\n", args[i]) | |
| 143 | return protocol.ExitUsage | |
| 144 | } | |
| 123 | f, err := parseFlags(argv[1:], flagSpec{Values: []string{"--username", "--email", "--invite"}, MaxPos: 0, | |
| 124 | Usage: "register --username <n> --email <a> | --invite <code>"}) | |
| 125 | if err != nil { | |
| 126 | fmt.Fprintln(stderr, err) | |
| 127 | return protocol.ExitUsage | |
| 145 | 128 | } |
| 129 | username, email, invite := f.Value("--username"), f.Value("--email"), f.Value("--invite") | |
| 146 | 130 | fail := func(code int, format string, a ...any) int { |
| 147 | 131 | fmt.Fprintf(stderr, format+"\n", a...) |
| 148 | 132 | return code |
internal/control/release.go +11 −55
| @@ -77,34 +77,12 @@ func releaseRef(c *Ctx, args []string, perm func(store.User, store.Repo, string) | ||
| 77 | 77 | |
| 78 | 78 | func runReleaseCreate(c *Ctx, args []string) int { |
| 79 | 79 | const usage = "usage: release create <owner/name> <tag> [--title <t>] [--notes <n> | --file -] [--format md|org]" |
| 80 | var path, tag, title, notes, file, format string | |
| 81 | for i := 0; i < len(args); i++ { | |
| 82 | switch args[i] { | |
| 83 | case "--title", "--notes", "--file", "--format": | |
| 84 | if i+1 >= len(args) { | |
| 85 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 86 | } | |
| 87 | switch args[i] { | |
| 88 | case "--title": | |
| 89 | title = args[i+1] | |
| 90 | case "--notes": | |
| 91 | notes = args[i+1] | |
| 92 | case "--file": | |
| 93 | file = args[i+1] | |
| 94 | case "--format": | |
| 95 | format = args[i+1] | |
| 96 | } | |
| 97 | i++ | |
| 98 | default: | |
| 99 | if path == "" { | |
| 100 | path = args[i] | |
| 101 | } else if tag == "" { | |
| 102 | tag = args[i] | |
| 103 | } else { | |
| 104 | return c.fail(protocol.ExitUsage, usage) | |
| 105 | } | |
| 106 | } | |
| 80 | f, err := parseFlags(args, flagSpec{Values: []string{"--title", "--notes", "--file", "--format"}, MaxPos: 2, Usage: usage}) | |
| 81 | if err != nil { | |
| 82 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 107 | 83 | } |
| 84 | 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") | |
| 108 | 86 | if path == "" || tag == "" { |
| 109 | 87 | return c.fail(protocol.ExitUsage, usage) |
| 110 | 88 | } |
| @@ -172,35 +150,13 @@ func releaseToOut(r store.Release, withNotes bool) releaseOut { | ||
| 172 | 150 | |
| 173 | 151 | func runReleaseEdit(c *Ctx, args []string) int { |
| 174 | 152 | const usage = "usage: release edit <owner/name> <tag> [--title <t>] [--notes <n> | --file -] [--format md|org]" |
| 175 | var path, tag, title, notes, file, format string | |
| 176 | var setTitle, setNotes bool | |
| 177 | for i := 0; i < len(args); i++ { | |
| 178 | switch args[i] { | |
| 179 | case "--title", "--notes", "--file", "--format": | |
| 180 | if i+1 >= len(args) { | |
| 181 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 182 | } | |
| 183 | switch args[i] { | |
| 184 | case "--title": | |
| 185 | title, setTitle = args[i+1], true | |
| 186 | case "--notes": | |
| 187 | notes, setNotes = args[i+1], true | |
| 188 | case "--file": | |
| 189 | file, setNotes = args[i+1], true | |
| 190 | case "--format": | |
| 191 | format = args[i+1] | |
| 192 | } | |
| 193 | i++ | |
| 194 | default: | |
| 195 | if path == "" { | |
| 196 | path = args[i] | |
| 197 | } else if tag == "" { | |
| 198 | tag = args[i] | |
| 199 | } else { | |
| 200 | return c.fail(protocol.ExitUsage, usage) | |
| 201 | } | |
| 202 | } | |
| 153 | f, err := parseFlags(args, flagSpec{Values: []string{"--title", "--notes", "--file", "--format"}, MaxPos: 2, Usage: usage}) | |
| 154 | if err != nil { | |
| 155 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 203 | 156 | } |
| 157 | path, tag := f.pos(0), f.pos(1) | |
| 158 | title, notes, file, format := f.Value("--title"), f.Value("--notes"), f.Value("--file"), f.Value("--format") | |
| 159 | setTitle, setNotes := f.Has("--title"), f.Has("--notes") || f.Has("--file") | |
| 204 | 160 | fmtName, err := markupFormat(format) |
| 205 | 161 | if err != nil { |
| 206 | 162 | return c.failErr(err) |
internal/control/repo.go +11 −36
| @@ -146,24 +146,13 @@ func resolveRepo(c *Ctx, path string, check func(store.User, store.Repo, string) | ||
| 146 | 146 | } |
| 147 | 147 | |
| 148 | 148 | func runRepoCreate(c *Ctx, args []string) int { |
| 149 | visibility := "public" | |
| 150 | var path, description string | |
| 151 | for i := 0; i < len(args); i++ { | |
| 152 | switch args[i] { | |
| 153 | case "--private": | |
| 154 | visibility = "private" | |
| 155 | case "--description": | |
| 156 | if i+1 >= len(args) { | |
| 157 | return c.fail(protocol.ExitUsage, "--description requires a value") | |
| 158 | } | |
| 159 | description = args[i+1] | |
| 160 | i++ | |
| 161 | default: | |
| 162 | if path != "" { | |
| 163 | return c.fail(protocol.ExitUsage, "usage: repo create <owner/name> [--private] [--description <text>]") | |
| 164 | } | |
| 165 | path = args[i] | |
| 166 | } | |
| 149 | f, err := parseFlags(args, flagSpec{Values: []string{"--description"}, Bools: []string{"--private"}, MaxPos: 1, Usage: "repo create <owner/name> [--private] [--description <text>]"}) | |
| 150 | if err != nil { | |
| 151 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 152 | } | |
| 153 | visibility, path, description := "public", f.pos(0), f.Value("--description") | |
| 154 | if f.Has("--private") { | |
| 155 | visibility = "private" | |
| 167 | 156 | } |
| 168 | 157 | owner, name, ok := strings.Cut(path, "/") |
| 169 | 158 | if !ok { |
| @@ -810,25 +799,11 @@ func matchesRepo(q string, r store.Repo, desc string, topics []string) bool { | ||
| 810 | 799 | } |
| 811 | 800 | |
| 812 | 801 | func runRepoGrep(c *Ctx, args []string) int { |
| 813 | var path, query, ref string | |
| 814 | for i := 0; i < len(args); i++ { | |
| 815 | switch args[i] { | |
| 816 | case "--ref": | |
| 817 | if i+1 >= len(args) { | |
| 818 | return c.fail(protocol.ExitUsage, "--ref requires a value") | |
| 819 | } | |
| 820 | ref = args[i+1] | |
| 821 | i++ | |
| 822 | default: | |
| 823 | if path == "" { | |
| 824 | path = args[i] | |
| 825 | } else if query == "" { | |
| 826 | query = args[i] | |
| 827 | } else { | |
| 828 | return c.fail(protocol.ExitUsage, "usage: repo grep <owner/name> <query> [--ref <ref>]") | |
| 829 | } | |
| 830 | } | |
| 802 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref"}, MaxPos: 2, Usage: "repo grep <owner/name> <query> [--ref <ref>]"}) | |
| 803 | if err != nil { | |
| 804 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 831 | 805 | } |
| 806 | path, query, ref := f.pos(0), f.pos(1), f.Value("--ref") | |
| 832 | 807 | if path == "" || query == "" { |
| 833 | 808 | return c.fail(protocol.ExitUsage, "usage: repo grep <owner/name> <query> [--ref <ref>]") |
| 834 | 809 | } |
internal/control/sig.go +10 −31
| @@ -127,38 +127,17 @@ func VerifyCommitCached(st *store.Store, repo store.Repo, parsed *sig.Commit, sh | ||
| 127 | 127 | } |
| 128 | 128 | |
| 129 | 129 | func runRepoLog(c *Ctx, args []string) int { |
| 130 | limit := 30 | |
| 131 | var path, filePath, ref string | |
| 132 | for i := 0; i < len(args); i++ { | |
| 133 | switch args[i] { | |
| 134 | case "--ref": | |
| 135 | if i+1 >= len(args) { | |
| 136 | return c.fail(protocol.ExitUsage, "--ref requires a value") | |
| 137 | } | |
| 138 | ref = args[i+1] | |
| 139 | i++ | |
| 140 | case "--limit": | |
| 141 | if i+1 >= len(args) { | |
| 142 | return c.fail(protocol.ExitUsage, "--limit requires a value") | |
| 143 | } | |
| 144 | n, err := strconv.Atoi(args[i+1]) | |
| 145 | if err != nil || n < 1 || n > 1000 { | |
| 146 | return c.fail(protocol.ExitUsage, "--limit must be 1..1000") | |
| 147 | } | |
| 148 | limit = n | |
| 149 | i++ | |
| 150 | case "--path": | |
| 151 | if i+1 >= len(args) { | |
| 152 | return c.fail(protocol.ExitUsage, "--path requires a value") | |
| 153 | } | |
| 154 | filePath = args[i+1] | |
| 155 | i++ | |
| 156 | default: | |
| 157 | if path != "" { | |
| 158 | return c.fail(protocol.ExitUsage, "usage: repo log <owner/name> [--ref <r>] [--limit n] [--path <file>]") | |
| 159 | } | |
| 160 | path = args[i] | |
| 130 | f, perr := parseFlags(args, flagSpec{Values: []string{"--ref", "--limit", "--path"}, MaxPos: 1, Usage: "repo log <owner/name> [--ref <r>] [--limit n] [--path <file>]"}) | |
| 131 | if perr != nil { | |
| 132 | return c.fail(protocol.ExitUsage, "%v", perr) | |
| 133 | } | |
| 134 | limit, path, filePath, ref := 30, f.pos(0), f.Value("--path"), f.Value("--ref") | |
| 135 | if f.Has("--limit") { | |
| 136 | n, err := strconv.Atoi(f.Value("--limit")) | |
| 137 | if err != nil || n < 1 || n > 1000 { | |
| 138 | return c.fail(protocol.ExitUsage, "--limit must be 1..1000") | |
| 161 | 139 | } |
| 140 | limit = n | |
| 162 | 141 | } |
| 163 | 142 | if path == "" { |
| 164 | 143 | return c.fail(protocol.ExitUsage, "usage: repo log <owner/name> [--ref <r>] [--limit n] [--path <file>]") |
internal/control/token.go +7 −19
| @@ -39,25 +39,13 @@ func parseTTL(s string) (time.Duration, error) { | ||
| 39 | 39 | } |
| 40 | 40 | |
| 41 | 41 | func runTokenCreate(c *Ctx, args []string) int { |
| 42 | name, scope, ttl := "", "full", "" | |
| 43 | for i := 0; i < len(args); i++ { | |
| 44 | switch args[i] { | |
| 45 | case "--name", "--scope", "--ttl": | |
| 46 | if i+1 >= len(args) { | |
| 47 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 48 | } | |
| 49 | switch args[i] { | |
| 50 | case "--name": | |
| 51 | name = args[i+1] | |
| 52 | case "--scope": | |
| 53 | scope = args[i+1] | |
| 54 | case "--ttl": | |
| 55 | ttl = args[i+1] | |
| 56 | } | |
| 57 | i++ | |
| 58 | default: | |
| 59 | return c.fail(protocol.ExitUsage, "usage: token create --name <n> [--scope full|read] [--ttl 30d]") | |
| 60 | } | |
| 42 | f, err := parseFlags(args, flagSpec{Values: []string{"--name", "--scope", "--ttl"}, MaxPos: 0, Usage: "token create --name <n> [--scope full|read] [--ttl 30d]"}) | |
| 43 | if err != nil { | |
| 44 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 45 | } | |
| 46 | name, scope, ttl := f.Value("--name"), "full", f.Value("--ttl") | |
| 47 | if f.Has("--scope") { | |
| 48 | scope = f.Value("--scope") | |
| 61 | 49 | } |
| 62 | 50 | if name == "" || (scope != "full" && scope != "read") { |
| 63 | 51 | return c.fail(protocol.ExitUsage, "usage: token create --name <n> [--scope full|read] [--ttl 30d]") |
internal/control/webhook.go +17 −41
| @@ -31,29 +31,13 @@ func init() { | ||
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | func runWebhookAdd(c *Ctx, args []string) int { |
| 34 | var path, url, secret string | |
| 35 | events := "*" | |
| 36 | for i := 0; i < len(args); i++ { | |
| 37 | switch args[i] { | |
| 38 | case "--secret", "--events": | |
| 39 | if i+1 >= len(args) { | |
| 40 | return c.fail(protocol.ExitUsage, "%s requires a value", args[i]) | |
| 41 | } | |
| 42 | if args[i] == "--secret" { | |
| 43 | secret = args[i+1] | |
| 44 | } else { | |
| 45 | events = args[i+1] | |
| 46 | } | |
| 47 | i++ | |
| 48 | default: | |
| 49 | if path == "" { | |
| 50 | path = args[i] | |
| 51 | } else if url == "" { | |
| 52 | url = args[i] | |
| 53 | } else { | |
| 54 | return c.fail(protocol.ExitUsage, "unexpected argument %q", args[i]) | |
| 55 | } | |
| 56 | } | |
| 34 | f, err := parseFlags(args, flagSpec{Values: []string{"--secret", "--events"}, MaxPos: 2, Usage: "webhook add <owner/name> <url> [--secret <s>] [--events push,issue.created|*]"}) | |
| 35 | if err != nil { | |
| 36 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 37 | } | |
| 38 | path, url, secret, events := f.pos(0), f.pos(1), f.Value("--secret"), "*" | |
| 39 | if f.Has("--events") { | |
| 40 | events = f.Value("--events") | |
| 57 | 41 | } |
| 58 | 42 | if path == "" || url == "" { |
| 59 | 43 | return c.fail(protocol.ExitUsage, "usage: webhook add <owner/name> <url> [--secret <s>] [--events <k1,k2>|*]") |
| @@ -128,25 +112,17 @@ func runWebhookRemove(c *Ctx, args []string) int { | ||
| 128 | 112 | } |
| 129 | 113 | |
| 130 | 114 | func runWebhookDeliveries(c *Ctx, args []string) int { |
| 131 | limit := 20 | |
| 132 | var path string | |
| 133 | for i := 0; i < len(args); i++ { | |
| 134 | if args[i] == "--limit" { | |
| 135 | if i+1 >= len(args) { | |
| 136 | return c.fail(protocol.ExitUsage, "--limit requires a value") | |
| 137 | } | |
| 138 | n, err := strconv.Atoi(args[i+1]) | |
| 139 | if err != nil || n < 1 || n > 200 { | |
| 140 | return c.fail(protocol.ExitUsage, "--limit must be 1..200") | |
| 141 | } | |
| 142 | limit = n | |
| 143 | i++ | |
| 144 | continue | |
| 145 | } | |
| 146 | if path != "" { | |
| 147 | return c.fail(protocol.ExitUsage, "usage: webhook deliveries <owner/name> [--limit n]") | |
| 115 | f, err := parseFlags(args, flagSpec{Values: []string{"--limit"}, MaxPos: 1, Usage: "webhook deliveries <owner/name> [--limit n]"}) | |
| 116 | if err != nil { | |
| 117 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 118 | } | |
| 119 | limit, path := 20, f.pos(0) | |
| 120 | if f.Has("--limit") { | |
| 121 | n, err := strconv.Atoi(f.Value("--limit")) | |
| 122 | if err != nil || n < 1 || n > 200 { | |
| 123 | return c.fail(protocol.ExitUsage, "--limit must be 1..200") | |
| 148 | 124 | } |
| 149 | path = args[i] | |
| 125 | limit = n | |
| 150 | 126 | } |
| 151 | 127 | if path == "" { |
| 152 | 128 | return c.fail(protocol.ExitUsage, "usage: webhook deliveries <owner/name> [--limit n]") |