Commit c5be40147c
Verified · cmc
internal/control/admin.go +57 −27
| @@ -16,59 +16,89 @@ import ( | ||
| 16 | 16 | |
| 17 | 17 | func init() { |
| 18 | 18 | register(Command{Path: []string{"admin", "user", "list"}, |
| 19 | Summary: "list accounts (instance admins)", | |
| 20 | Usage: "admin user list [--state active|pending|disabled|admin] [--limit <n>] [--cursor <c>]", | |
| 19 | Summary: "list accounts (instance admins)", | |
| 20 | Usage: "admin user list [--state active|pending|disabled|admin] [--limit <n>] [--cursor <c>]", | |
| 21 | Flags: []Flag{ | |
| 22 | {"--state", "active|pending|disabled|admin", "which accounts", ""}, | |
| 23 | {"--limit", "<n>", "rows per page", ""}, | |
| 24 | {"--cursor", "<c>", "continue from the previous page", ""}, | |
| 25 | }, | |
| 26 | Examples: []string{"admin user list --state pending"}, | |
| 21 | 27 | ReadOnly: true, Run: runAdminUserList}) |
| 22 | 28 | register(Command{Path: []string{"admin", "user", "show"}, |
| 23 | 29 | Summary: "show an account: keys, emails, orgs, tokens, sessions (instance admins)", |
| 24 | 30 | Usage: "admin user show <username>", |
| 31 | Examples: []string{"admin user show alice"}, | |
| 25 | 32 | ReadOnly: true, Run: runAdminUserShow}) |
| 26 | 33 | register(Command{Path: []string{"admin", "user", "promote"}, |
| 27 | Summary: "make an account an instance admin", | |
| 28 | Usage: "admin user promote <username>", | |
| 29 | Run: runAdminUserPromote}) | |
| 34 | Summary: "make an account an instance admin", | |
| 35 | Usage: "admin user promote <username>", | |
| 36 | Examples: []string{"admin user promote alice"}, | |
| 37 | Run: runAdminUserPromote}) | |
| 30 | 38 | register(Command{Path: []string{"admin", "user", "demote"}, |
| 31 | Summary: "remove instance admin from an account (never the last one)", | |
| 32 | Usage: "admin user demote <username>", | |
| 33 | Run: runAdminUserDemote}) | |
| 39 | Summary: "remove instance admin from an account (never the last one)", | |
| 40 | Usage: "admin user demote <username>", | |
| 41 | Examples: []string{"admin user demote alice"}, | |
| 42 | Run: runAdminUserDemote}) | |
| 34 | 43 | register(Command{Path: []string{"admin", "runners"}, |
| 35 | 44 | Summary: "the build queue and runner accounts: last poll, scope, the build each holds (instance admins)", |
| 36 | 45 | Usage: "admin runners", |
| 46 | Examples: []string{"admin runners"}, | |
| 37 | 47 | ReadOnly: true, Run: runAdminRunners}) |
| 38 | 48 | register(Command{Path: []string{"admin", "runners", "remove"}, |
| 39 | Summary: "drop a key's runner heartbeat row, e.g. one that polled once by mistake (instance admins)", | |
| 40 | Usage: "admin runners remove <fingerprint>", | |
| 41 | Run: runAdminRunnersForget}) | |
| 49 | Summary: "drop a key's runner heartbeat row, e.g. one that polled once by mistake (instance admins)", | |
| 50 | Usage: "admin runners remove <fingerprint>", | |
| 51 | Examples: []string{"admin runners remove SHA256:abcd1234"}, | |
| 52 | Run: runAdminRunnersForget}) | |
| 42 | 53 | // forget is the name this shipped under in v1.18; remove is the verb |
| 43 | 54 | // every other noun uses. Both stay for one release. |
| 44 | 55 | register(Command{Path: []string{"admin", "runners", "forget"}, |
| 45 | Summary: "alias of admin runners remove", | |
| 46 | Usage: "admin runners forget <fingerprint>", | |
| 47 | Run: runAdminRunnersForget}) | |
| 56 | Summary: "alias of admin runners remove", | |
| 57 | Usage: "admin runners forget <fingerprint>", | |
| 58 | Examples: []string{"admin runners forget SHA256:abcd1234"}, | |
| 59 | Run: runAdminRunnersForget}) | |
| 48 | 60 | register(Command{Path: []string{"admin", "repo", "list"}, |
| 49 | Summary: "list every repository with size and last push (instance admins)", | |
| 50 | Usage: "admin repo list [--owner <name>] [--visibility public|private] [--limit <n>] [--cursor <c>]", | |
| 61 | Summary: "list every repository with size and last push (instance admins)", | |
| 62 | Usage: "admin repo list [--owner <name>] [--visibility public|private] [--limit <n>] [--cursor <c>]", | |
| 63 | Flags: []Flag{ | |
| 64 | {"--owner", "<name>", "only this owner's repositories", ""}, | |
| 65 | {"--visibility", "public|private", "which repositories", ""}, | |
| 66 | {"--limit", "<n>", "rows per page", ""}, | |
| 67 | {"--cursor", "<c>", "continue from the previous page", ""}, | |
| 68 | }, | |
| 69 | Examples: []string{"admin repo list --owner alice"}, | |
| 51 | 70 | ReadOnly: true, Run: runAdminRepoList}) |
| 52 | 71 | register(Command{Path: []string{"admin", "repo", "archive"}, |
| 53 | Summary: "archive any repository (instance admins; audited)", | |
| 54 | Usage: "admin repo archive <owner/name>", | |
| 55 | Run: runAdminRepoArchive}) | |
| 72 | Summary: "archive any repository (instance admins; audited)", | |
| 73 | Usage: "admin repo archive <owner/name>", | |
| 74 | Examples: []string{"admin repo archive alice/old-project"}, | |
| 75 | Run: runAdminRepoArchive}) | |
| 56 | 76 | register(Command{Path: []string{"admin", "repo", "unarchive"}, |
| 57 | Summary: "unarchive any repository (instance admins; audited)", | |
| 58 | Usage: "admin repo unarchive <owner/name>", | |
| 59 | Run: runAdminRepoUnarchive}) | |
| 77 | Summary: "unarchive any repository (instance admins; audited)", | |
| 78 | Usage: "admin repo unarchive <owner/name>", | |
| 79 | Examples: []string{"admin repo unarchive alice/old-project"}, | |
| 80 | Run: runAdminRepoUnarchive}) | |
| 60 | 81 | register(Command{Path: []string{"admin", "repo", "visibility"}, |
| 61 | Summary: "set any repository's visibility (instance admins; audited)", | |
| 62 | Usage: "admin repo visibility <owner/name> public|private", | |
| 63 | Run: runAdminRepoVisibility}) | |
| 82 | Summary: "set any repository's visibility (instance admins; audited)", | |
| 83 | Usage: "admin repo visibility <owner/name> public|private", | |
| 84 | Examples: []string{"admin repo visibility alice/secret private"}, | |
| 85 | Run: runAdminRepoVisibility}) | |
| 64 | 86 | register(Command{Path: []string{"admin", "repo", "delete"}, |
| 65 | 87 | Summary: "delete any repository (instance admins; audited)", |
| 66 | 88 | Usage: "admin repo delete <owner/name> --yes", |
| 67 | Run: runAdminRepoDelete}) | |
| 89 | Flags: []Flag{ | |
| 90 | {"--yes", "", "confirm the permanent delete", ""}, | |
| 91 | }, | |
| 92 | Examples: []string{"admin repo delete alice/spam --yes"}, | |
| 93 | Run: runAdminRepoDelete}) | |
| 68 | 94 | register(Command{Path: []string{"admin", "mr", "prune"}, |
| 69 | 95 | Summary: "drop merged or closed MRs' head refs and the objects only they kept, e.g. after a history rewrite (instance admins; audited)", |
| 70 | 96 | Usage: "admin mr prune <owner/name> <n> [<n>...] --yes", |
| 71 | Run: runAdminMRPrune}) | |
| 97 | Flags: []Flag{ | |
| 98 | {"--yes", "", "confirm the permanent prune", ""}, | |
| 99 | }, | |
| 100 | Examples: []string{"admin mr prune krz/gitbay 12 13 --yes"}, | |
| 101 | Run: runAdminMRPrune}) | |
| 72 | 102 | } |
| 73 | 103 | |
| 74 | 104 | // requireInstanceAdmin gates the admin noun. -1 means proceed. |
internal/control/adminhost.go +32 −13
| @@ -22,32 +22,51 @@ import ( | ||
| 22 | 22 | |
| 23 | 23 | func init() { |
| 24 | 24 | register(Command{Path: []string{"admin", "user", "create"}, |
| 25 | Summary: "create an account, optionally with a key and a verified address (instance admins)", | |
| 26 | Usage: "admin user create <username> [--admin] [--email <address> [--verified]] [--key -] < key.pub", | |
| 25 | Summary: "create an account, optionally with a key and a verified address (instance admins)", | |
| 26 | Usage: "admin user create <username> [--admin] [--email <address> [--verified]] [--key -] < key.pub", | |
| 27 | Flags: []Flag{ | |
| 28 | {"--admin", "", "make the account an instance admin", ""}, | |
| 29 | {"--email", "<address>", "an address to add", ""}, | |
| 30 | {"--verified", "", "mark that address verified", ""}, | |
| 31 | {"--key", "-", "read a public key from stdin", ""}, | |
| 32 | }, | |
| 33 | Examples: []string{"admin user create alice --email alice@example.org --key - < key.pub"}, | |
| 27 | 34 | ReadsStdin: true, Run: runAdminUserCreate}) |
| 28 | 35 | register(Command{Path: []string{"admin", "user", "disable"}, |
| 29 | Summary: "suspend an account: SSH, web sessions and API tokens refused until re-enabled", | |
| 30 | Usage: "admin user disable <username>", | |
| 31 | Run: runAdminUserDisable}) | |
| 36 | Summary: "suspend an account: SSH, web sessions and API tokens refused until re-enabled", | |
| 37 | Usage: "admin user disable <username>", | |
| 38 | Examples: []string{"admin user disable alice"}, | |
| 39 | Run: runAdminUserDisable}) | |
| 32 | 40 | register(Command{Path: []string{"admin", "user", "enable"}, |
| 33 | Summary: "restore a suspended account", | |
| 34 | Usage: "admin user enable <username>", | |
| 35 | Run: runAdminUserEnable}) | |
| 41 | Summary: "restore a suspended account", | |
| 42 | Usage: "admin user enable <username>", | |
| 43 | Examples: []string{"admin user enable alice"}, | |
| 44 | Run: runAdminUserEnable}) | |
| 36 | 45 | register(Command{Path: []string{"admin", "user", "delete"}, |
| 37 | 46 | Summary: "delete an account that anchors nothing (keys, emails and sessions go with it)", |
| 38 | 47 | Usage: "admin user delete <username> --yes", |
| 39 | Run: runAdminUserDelete}) | |
| 48 | Flags: []Flag{ | |
| 49 | {"--yes", "", "confirm the permanent delete", ""}, | |
| 50 | }, | |
| 51 | Examples: []string{"admin user delete alice --yes"}, | |
| 52 | Run: runAdminUserDelete}) | |
| 40 | 53 | register(Command{Path: []string{"admin", "email", "verify"}, |
| 41 | Summary: "mark an address verified by admin assertion", | |
| 42 | Usage: "admin email verify <username> <address>", | |
| 43 | Run: runAdminEmailVerify}) | |
| 54 | Summary: "mark an address verified by admin assertion", | |
| 55 | Usage: "admin email verify <username> <address>", | |
| 56 | Examples: []string{"admin email verify alice alice@example.org"}, | |
| 57 | Run: runAdminEmailVerify}) | |
| 44 | 58 | register(Command{Path: []string{"admin", "invite"}, |
| 45 | 59 | Summary: "issue a registration invite and mail its code", |
| 46 | 60 | Usage: "admin invite --email <address>", |
| 47 | Run: runAdminInvite}) | |
| 61 | Flags: []Flag{ | |
| 62 | {"--email", "<address>", "who the invite is for", ""}, | |
| 63 | }, | |
| 64 | Examples: []string{"admin invite --email alice@example.org"}, | |
| 65 | Run: runAdminInvite}) | |
| 48 | 66 | register(Command{Path: []string{"admin", "stats"}, |
| 49 | 67 | Summary: "instance statistics: counts and per-repository disk usage", |
| 50 | 68 | Usage: "admin stats", |
| 69 | Examples: []string{"admin stats"}, | |
| 51 | 70 | ReadOnly: true, Run: runAdminStats}) |
| 52 | 71 | } |
| 53 | 72 | |
internal/control/audit.go +9 −2
| @@ -12,8 +12,15 @@ import ( | ||
| 12 | 12 | |
| 13 | 13 | func init() { |
| 14 | 14 | register(Command{Path: []string{"audit"}, |
| 15 | Summary: "instance audit log (admins)", | |
| 16 | Usage: "audit [--actor <user>|-] [--action <prefix>] [--since <duration|date>] [--limit <n>]", | |
| 15 | Summary: "instance audit log (admins)", | |
| 16 | Usage: "audit [--actor <user>|-] [--action <prefix>] [--since <duration|date>] [--limit <n>]", | |
| 17 | Flags: []Flag{ | |
| 18 | {"--actor", "<user>|-", "only entries by this user", ""}, | |
| 19 | {"--action", "<prefix>", "only actions starting with this", ""}, | |
| 20 | {"--since", "<duration|date>", "only entries after this", ""}, | |
| 21 | {"--limit", "<n>", "rows to show", "100"}, | |
| 22 | }, | |
| 23 | Examples: []string{"audit --actor alice --since 24h"}, | |
| 17 | 24 | ReadOnly: true, Run: runAudit}) |
| 18 | 25 | } |
| 19 | 26 | |
internal/control/control.go +1
| @@ -330,6 +330,7 @@ func init() { | ||
| 330 | 330 | Path: []string{"help"}, |
| 331 | 331 | Summary: "list available commands", |
| 332 | 332 | Usage: "help [<prefix>...]", |
| 333 | Examples: []string{"help", "help repo"}, | |
| 333 | 334 | ReadOnly: true, |
| 334 | 335 | Run: runHelp, |
| 335 | 336 | }) |
internal/control/help_test.go −24
| @@ -17,35 +17,11 @@ var usageFlag = regexp.MustCompile(`--[a-z][a-z0-9-]*`) | ||
| 17 | 17 | // is not something the command's own tokenizer parses. |
| 18 | 18 | var trailingRedirect = regexp.MustCompile(`^(.*?)\s+[<>]\s*\S+$`) |
| 19 | 19 | |
| 20 | // helpCovered restricts TestHelpIsComplete to the path prefixes whose help | |
| 21 | // text this commit filled in. Task 4.3 fills the rest and removes this set. | |
| 22 | // "org label" and "org milestone" name the two prefixes under "org" this | |
| 23 | // commit covers; the rest of "org" (teams, members, profile) is not. | |
| 24 | // widened to every command in the next commit | |
| 25 | var helpCovered = []string{ | |
| 26 | "issue", "mr", "build", "release", "milestone", "label", "search", | |
| 27 | "dashboard", "feed", "status", "wiki", "snippet", "repo", | |
| 28 | "org label", "org milestone", | |
| 29 | } | |
| 30 | ||
| 31 | func isHelpCovered(path []string) bool { | |
| 32 | joined := strings.Join(path, " ") | |
| 33 | for _, prefix := range helpCovered { | |
| 34 | if joined == prefix || strings.HasPrefix(joined, prefix+" ") { | |
| 35 | return true | |
| 36 | } | |
| 37 | } | |
| 38 | return false | |
| 39 | } | |
| 40 | ||
| 41 | 20 | // Help is written once, in the registry. Every flag in a usage line has |
| 42 | 21 | // a description, every description names a flag in the usage line, and |
| 43 | 22 | // every command has an example that runs it. |
| 44 | 23 | func TestHelpIsComplete(t *testing.T) { |
| 45 | 24 | for _, cmd := range Commands() { |
| 46 | if !isHelpCovered(cmd.Path) { | |
| 47 | continue | |
| 48 | } | |
| 49 | 25 | path := strings.Join(cmd.Path, " ") |
| 50 | 26 | inUsage := map[string]bool{} |
| 51 | 27 | for _, f := range usageFlag.FindAllString(cmd.Usage, -1) { |
internal/control/identity.go +20 −11
| @@ -18,6 +18,7 @@ func init() { | ||
| 18 | 18 | Path: []string{"whoami"}, |
| 19 | 19 | Summary: "show the authenticated account", |
| 20 | 20 | Usage: "whoami", |
| 21 | Examples: []string{"whoami"}, | |
| 21 | 22 | ReadOnly: true, |
| 22 | 23 | Run: runWhoami, |
| 23 | 24 | }) |
| @@ -25,27 +26,35 @@ func init() { | ||
| 25 | 26 | Path: []string{"keys", "list"}, |
| 26 | 27 | Summary: "list registered SSH keys", |
| 27 | 28 | Usage: "keys list", |
| 29 | Examples: []string{"keys list"}, | |
| 28 | 30 | ReadOnly: true, |
| 29 | 31 | Run: runKeysList, |
| 30 | 32 | }) |
| 31 | 33 | register(Command{ |
| 32 | Path: []string{"keys", "add"}, | |
| 33 | Summary: "register an SSH public key (authorized_keys format)", | |
| 34 | Usage: "keys add [--scope full|git|runner] [--label <text>] < key.pub", | |
| 34 | Path: []string{"keys", "add"}, | |
| 35 | Summary: "register an SSH public key (authorized_keys format)", | |
| 36 | Usage: "keys add [--scope full|git|runner] [--label <text>] < key.pub", | |
| 37 | Flags: []Flag{ | |
| 38 | {"--scope", "full|git|runner", "what the key may do", "full"}, | |
| 39 | {"--label", "<text>", "a name for the key", ""}, | |
| 40 | }, | |
| 41 | Examples: []string{"keys add --label laptop < key.pub"}, | |
| 35 | 42 | ReadsStdin: true, |
| 36 | 43 | Run: runKeysAdd, |
| 37 | 44 | }) |
| 38 | 45 | register(Command{ |
| 39 | Path: []string{"keys", "label"}, | |
| 40 | Summary: "name a key; an empty label clears it", | |
| 41 | Usage: "keys label <fingerprint> [<text>]", | |
| 42 | Run: runKeysLabel, | |
| 46 | Path: []string{"keys", "label"}, | |
| 47 | Summary: "name a key; an empty label clears it", | |
| 48 | Usage: "keys label <fingerprint> [<text>]", | |
| 49 | Examples: []string{`keys label SHA256:abcd1234 "work laptop"`}, | |
| 50 | Run: runKeysLabel, | |
| 43 | 51 | }) |
| 44 | 52 | register(Command{ |
| 45 | Path: []string{"keys", "remove"}, | |
| 46 | Summary: "remove an SSH key by fingerprint", | |
| 47 | Usage: "keys remove <fingerprint>", | |
| 48 | Run: runKeysRemove, | |
| 53 | Path: []string{"keys", "remove"}, | |
| 54 | Summary: "remove an SSH key by fingerprint", | |
| 55 | Usage: "keys remove <fingerprint>", | |
| 56 | Examples: []string{"keys remove SHA256:abcd1234"}, | |
| 57 | Run: runKeysRemove, | |
| 49 | 58 | }) |
| 50 | 59 | } |
| 51 | 60 | |
internal/control/migrate.go +7 −2
| @@ -16,10 +16,15 @@ func init() { | ||
| 16 | 16 | register(Command{Path: []string{"account", "export"}, |
| 17 | 17 | Summary: "write your account bundle (profile, repos, issues, MRs) as JSON", |
| 18 | 18 | Usage: "account export > bundle.json", |
| 19 | Examples: []string{"account export > bundle.json"}, | |
| 19 | 20 | ReadOnly: true, Run: runAccountExport}) |
| 20 | 21 | register(Command{Path: []string{"account", "import-bundle"}, |
| 21 | Summary: "replay an account bundle (see gitbay migrate)", | |
| 22 | Usage: "account import-bundle [--source <host>] < bundle.json", | |
| 22 | Summary: "replay an account bundle (see gitbay migrate)", | |
| 23 | Usage: "account import-bundle [--source <host>] < bundle.json", | |
| 24 | Flags: []Flag{ | |
| 25 | {"--source", "<host>", "the previous instance, for attribution", ""}, | |
| 26 | }, | |
| 27 | Examples: []string{"account import-bundle --source old.example.org < bundle.json"}, | |
| 23 | 28 | ReadsStdin: true, Run: runAccountImportBundle}) |
| 24 | 29 | } |
| 25 | 30 | |
internal/control/notifications.go +36 −11
| @@ -15,38 +15,63 @@ import ( | ||
| 15 | 15 | |
| 16 | 16 | func init() { |
| 17 | 17 | register(Command{Path: []string{"notifications", "list"}, |
| 18 | Summary: "your notification inbox, newest first", | |
| 19 | Usage: "notifications list [--all] [--limit <n>] [--cursor <c>]", | |
| 18 | Summary: "your notification inbox, newest first", | |
| 19 | Usage: "notifications list [--all] [--limit <n>] [--cursor <c>]", | |
| 20 | Flags: []Flag{ | |
| 21 | {"--all", "", "include already-read notifications", ""}, | |
| 22 | {"--limit", "<n>", "rows per page", ""}, | |
| 23 | {"--cursor", "<c>", "continue from the previous page", ""}, | |
| 24 | }, | |
| 25 | Examples: []string{"notifications list", "notifications list --all --limit 50"}, | |
| 20 | 26 | ReadOnly: true, Run: runNotificationsList}) |
| 21 | 27 | register(Command{Path: []string{"notifications", "read"}, |
| 22 | 28 | Summary: "mark notifications read", |
| 23 | Usage: "notifications read <id>... | --all", Run: runNotificationsRead}) | |
| 29 | Usage: "notifications read <id>... | --all", | |
| 30 | Flags: []Flag{ | |
| 31 | {"--all", "", "mark every unread notification read", ""}, | |
| 32 | }, | |
| 33 | Examples: []string{"notifications read 12 13", "notifications read --all"}, | |
| 34 | Run: runNotificationsRead}) | |
| 24 | 35 | register(Command{Path: []string{"notifications", "settings", "show"}, |
| 25 | 36 | Summary: "your notification preferences", |
| 26 | 37 | Usage: "notifications settings show", |
| 38 | Examples: []string{"notifications settings show"}, | |
| 27 | 39 | ReadOnly: true, Run: runNotificationsSettingsShow}) |
| 28 | 40 | register(Command{Path: []string{"notifications", "settings", "mail"}, |
| 29 | Summary: "activity by mail as well as the inbox (login links are unaffected)", | |
| 30 | Usage: "notifications settings mail on|off", Run: runNotificationsSettingsMail}) | |
| 41 | Summary: "activity by mail as well as the inbox (login links are unaffected)", | |
| 42 | Usage: "notifications settings mail on|off", | |
| 43 | Examples: []string{"notifications settings mail on"}, | |
| 44 | Run: runNotificationsSettingsMail}) | |
| 31 | 45 | register(Command{Path: []string{"notifications", "settings", "watch"}, |
| 32 | Summary: "every issue and merge request on repositories you can write to", | |
| 33 | Usage: "notifications settings watch on|off", Run: runNotificationsSettingsWatch}) | |
| 46 | Summary: "every issue and merge request on repositories you can write to", | |
| 47 | Usage: "notifications settings watch on|off", | |
| 48 | Examples: []string{"notifications settings watch on"}, | |
| 49 | Run: runNotificationsSettingsWatch}) | |
| 34 | 50 | register(Command{Path: []string{"notifications", "device", "add"}, |
| 35 | 51 | Summary: "register an Apple device for push, token on stdin", |
| 36 | 52 | Usage: "notifications device add [--label <name>] < token", |
| 53 | Flags: []Flag{ | |
| 54 | {"--label", "<name>", "a name for the device", ""}, | |
| 55 | }, | |
| 56 | Examples: []string{"notifications device add --label iphone < token"}, | |
| 37 | 57 | // Mandatory: without it control.go swaps in an empty reader and |
| 38 | 58 | // this command stores an empty token without erroring. |
| 39 | 59 | ReadsStdin: true, Run: runNotificationsDeviceAdd}) |
| 40 | 60 | register(Command{Path: []string{"notifications", "device", "list"}, |
| 41 | 61 | Summary: "your registered devices", |
| 42 | 62 | Usage: "notifications device list", |
| 63 | Examples: []string{"notifications device list"}, | |
| 43 | 64 | ReadOnly: true, Run: runNotificationsDeviceList}) |
| 44 | 65 | register(Command{Path: []string{"notifications", "device", "remove"}, |
| 45 | Summary: "deregister a device", | |
| 46 | Usage: "notifications device remove <id>", Run: runNotificationsDeviceRemove}) | |
| 66 | Summary: "deregister a device", | |
| 67 | Usage: "notifications device remove <id>", | |
| 68 | Examples: []string{"notifications device remove 4"}, | |
| 69 | Run: runNotificationsDeviceRemove}) | |
| 47 | 70 | register(Command{Path: []string{"notifications", "settings", "push"}, |
| 48 | Summary: "activity on your registered devices as well as the inbox", | |
| 49 | Usage: "notifications settings push on|off", Run: runNotificationsSettingsPush}) | |
| 71 | Summary: "activity on your registered devices as well as the inbox", | |
| 72 | Usage: "notifications settings push on|off", | |
| 73 | Examples: []string{"notifications settings push on"}, | |
| 74 | Run: runNotificationsSettingsPush}) | |
| 50 | 75 | register(Command{Path: []string{"repo", "watch"}, |
| 51 | 76 | Summary: "hear about all activity on a repository", |
| 52 | 77 | Usage: "repo watch <owner/name>", |
internal/control/org.go +28 −14
| @@ -14,29 +14,43 @@ import ( | ||
| 14 | 14 | |
| 15 | 15 | func init() { |
| 16 | 16 | register(Command{Path: []string{"org", "create"}, |
| 17 | Summary: "create an organization (you become its first admin)", | |
| 18 | Usage: "org create <name>", Run: runOrgCreate}) | |
| 17 | Summary: "create an organization (you become its first admin)", | |
| 18 | Usage: "org create <name>", | |
| 19 | Examples: []string{"org create krz"}, Run: runOrgCreate}) | |
| 19 | 20 | register(Command{Path: []string{"org", "list"}, |
| 20 | Summary: "list organizations you belong to", | |
| 21 | Usage: "org list", ReadOnly: true, Run: runOrgList}) | |
| 21 | Summary: "list organizations you belong to", | |
| 22 | Usage: "org list", | |
| 23 | Examples: []string{"org list"}, ReadOnly: true, Run: runOrgList}) | |
| 22 | 24 | register(Command{Path: []string{"org", "show"}, |
| 23 | Summary: "show an organization and its members", | |
| 24 | Usage: "org show <name>", ReadOnly: true, Run: runOrgShow}) | |
| 25 | Summary: "show an organization and its members", | |
| 26 | Usage: "org show <name>", | |
| 27 | Examples: []string{"org show krz"}, ReadOnly: true, Run: runOrgShow}) | |
| 25 | 28 | register(Command{Path: []string{"org", "rename"}, |
| 26 | Summary: "rename an organization", | |
| 27 | Usage: "org rename <old> <new> (clone URLs change)", Run: runOrgRename}) | |
| 29 | Summary: "rename an organization", | |
| 30 | Usage: "org rename <old> <new> (clone URLs change)", | |
| 31 | Examples: []string{"org rename krz krazywarez"}, Run: runOrgRename}) | |
| 28 | 32 | register(Command{Path: []string{"org", "delete"}, |
| 29 | 33 | Summary: "delete an empty organization", |
| 30 | Usage: "org delete <name> --yes", Run: runOrgDelete}) | |
| 34 | Usage: "org delete <name> --yes", | |
| 35 | Flags: []Flag{ | |
| 36 | {"--yes", "", "confirm the permanent delete", ""}, | |
| 37 | }, | |
| 38 | Examples: []string{"org delete krz --yes"}, Run: runOrgDelete}) | |
| 31 | 39 | register(Command{Path: []string{"org", "members", "add"}, |
| 32 | 40 | Summary: "add or update a member", |
| 33 | Usage: "org members add <org> <user> [--role member|admin]", Run: runOrgMembersAdd}) | |
| 41 | Usage: "org members add <org> <user> [--role member|admin]", | |
| 42 | Flags: []Flag{ | |
| 43 | {"--role", "member|admin", "the member's role", "member"}, | |
| 44 | }, | |
| 45 | Examples: []string{"org members add krz cmc --role admin"}, Run: runOrgMembersAdd}) | |
| 34 | 46 | register(Command{Path: []string{"org", "members", "remove"}, |
| 35 | Summary: "remove a member", | |
| 36 | Usage: "org members remove <org> <user>", Run: runOrgMembersRemove}) | |
| 47 | Summary: "remove a member", | |
| 48 | Usage: "org members remove <org> <user>", | |
| 49 | Examples: []string{"org members remove krz cmc"}, Run: runOrgMembersRemove}) | |
| 37 | 50 | register(Command{Path: []string{"org", "members", "list"}, |
| 38 | Summary: "list members", | |
| 39 | Usage: "org members list <org>", ReadOnly: true, Run: runOrgMembersList}) | |
| 51 | Summary: "list members", | |
| 52 | Usage: "org members list <org>", | |
| 53 | Examples: []string{"org members list krz"}, ReadOnly: true, Run: runOrgMembersList}) | |
| 40 | 54 | } |
| 41 | 55 | |
| 42 | 56 | // orgAdmin loads an org and requires the caller to be one of its admins. |
internal/control/profile.go +17 −4
| @@ -16,14 +16,27 @@ import ( | ||
| 16 | 16 | |
| 17 | 17 | func init() { |
| 18 | 18 | register(Command{Path: []string{"profile", "show"}, |
| 19 | Summary: "show a user's or org's profile", | |
| 20 | Usage: "profile show [name]", ReadOnly: true, Run: runProfileShow}) | |
| 19 | Summary: "show a user's or org's profile", | |
| 20 | Usage: "profile show [name]", | |
| 21 | Examples: []string{"profile show cmc"}, ReadOnly: true, Run: runProfileShow}) | |
| 21 | 22 | register(Command{Path: []string{"profile", "set"}, |
| 22 | 23 | Summary: "set your profile", |
| 23 | Usage: "profile set [--description <d>] [--website <url>] [--link <label|url>]... ('' clears)", Run: runProfileSet}) | |
| 24 | Usage: "profile set [--description <d>] [--website <url>] [--link <label|url>]... ('' clears)", | |
| 25 | Flags: []Flag{ | |
| 26 | {"--description", "<d>", "one line about you", ""}, | |
| 27 | {"--website", "<url>", "your website", ""}, | |
| 28 | {"--link", "<label|url>", "a profile link, may repeat", ""}, | |
| 29 | }, | |
| 30 | Examples: []string{`profile set --description "gitbay's author" --website https://cleberg.net`}, Run: runProfileSet}) | |
| 24 | 31 | register(Command{Path: []string{"org", "profile"}, |
| 25 | 32 | Summary: "show or set an org's profile", |
| 26 | Usage: "org profile <org> [--description <d>] [--website <url>] [--link <label|url>]...", Run: runOrgProfile}) | |
| 33 | Usage: "org profile <org> [--description <d>] [--website <url>] [--link <label|url>]...", | |
| 34 | Flags: []Flag{ | |
| 35 | {"--description", "<d>", "one line about the org", ""}, | |
| 36 | {"--website", "<url>", "the org's website", ""}, | |
| 37 | {"--link", "<label|url>", "a profile link, may repeat", ""}, | |
| 38 | }, | |
| 39 | Examples: []string{"org profile krz", `org profile krz --description "a self-hosted forge"`}, Run: runOrgProfile}) | |
| 27 | 40 | } |
| 28 | 41 | |
| 29 | 42 | // maxProfileLinks caps the free-form link list. A profile is a header, |
internal/control/quota.go +6 −1
| @@ -85,7 +85,12 @@ func init() { | ||
| 85 | 85 | register(Command{Path: []string{"admin", "user", "limits"}, |
| 86 | 86 | Summary: "show or set an account's repository and storage caps (instance admins)", |
| 87 | 87 | Usage: "admin user limits <username> [--repos <n>|default] [--bytes <n>|default]", |
| 88 | Run: runAdminUserLimits}) | |
| 88 | Flags: []Flag{ | |
| 89 | {"--repos", "<n>|default", "the account's repository cap", ""}, | |
| 90 | {"--bytes", "<n>|default", "the account's storage cap", ""}, | |
| 91 | }, | |
| 92 | Examples: []string{"admin user limits alice", "admin user limits alice --repos 50"}, | |
| 93 | Run: runAdminUserLimits}) | |
| 89 | 94 | } |
| 90 | 95 | |
| 91 | 96 | func runAdminUserLimits(c *Ctx, args []string) int { |
internal/control/register.go +19 −8
| @@ -20,27 +20,38 @@ func init() { | ||
| 20 | 20 | register(Command{Path: []string{"register"}, |
| 21 | 21 | Summary: "create an account (only meaningful for unregistered keys)", |
| 22 | 22 | Usage: "register --username <name> [--email <address> | --invite <code>]", |
| 23 | Flags: []Flag{ | |
| 24 | {"--username", "<name>", "the account's username", ""}, | |
| 25 | {"--email", "<address>", "for open registration", ""}, | |
| 26 | {"--invite", "<code>", "for invite-only registration", ""}, | |
| 27 | }, | |
| 28 | Examples: []string{"register --username cmc --email cmc@example.org"}, | |
| 23 | 29 | Run: func(c *Ctx, args []string) int { |
| 24 | 30 | return c.fail(protocol.ExitUsage, |
| 25 | 31 | "this SSH key already belongs to %s. To register a new account, connect with the key it should use:\n ssh -F /dev/null -i <newkey> git@<host> register ...", |
| 26 | 32 | c.User.Username) |
| 27 | 33 | }}) |
| 28 | 34 | register(Command{Path: []string{"email", "add"}, |
| 29 | Summary: "add an address and mail a verification code", | |
| 30 | Usage: "email add <address>", Run: runEmailAdd}) | |
| 35 | Summary: "add an address and mail a verification code", | |
| 36 | Usage: "email add <address>", | |
| 37 | Examples: []string{"email add cmc@example.org"}, Run: runEmailAdd}) | |
| 31 | 38 | register(Command{Path: []string{"email", "verify"}, |
| 32 | Summary: "confirm a verification code", | |
| 33 | Usage: "email verify <code>", Run: runEmailVerify}) | |
| 39 | Summary: "confirm a verification code", | |
| 40 | Usage: "email verify <code>", | |
| 41 | Examples: []string{"email verify abc123"}, Run: runEmailVerify}) | |
| 34 | 42 | register(Command{Path: []string{"email", "list"}, |
| 35 | 43 | Summary: "list the addresses on your account", |
| 36 | 44 | Usage: "email list", |
| 45 | Examples: []string{"email list"}, | |
| 37 | 46 | ReadOnly: true, Run: runEmailList}) |
| 38 | 47 | register(Command{Path: []string{"email", "remove"}, |
| 39 | Summary: "remove an address; not the primary, nor the last verified one", | |
| 40 | Usage: "email remove <address>", Run: runEmailRemove}) | |
| 48 | Summary: "remove an address; not the primary, nor the last verified one", | |
| 49 | Usage: "email remove <address>", | |
| 50 | Examples: []string{"email remove old@example.org"}, Run: runEmailRemove}) | |
| 41 | 51 | register(Command{Path: []string{"email", "primary"}, |
| 42 | Summary: "make a verified address the primary", | |
| 43 | Usage: "email primary <address>", Run: runEmailPrimary}) | |
| 52 | Summary: "make a verified address the primary", | |
| 53 | Usage: "email primary <address>", | |
| 54 | Examples: []string{"email primary cmc@example.org"}, Run: runEmailPrimary}) | |
| 44 | 55 | } |
| 45 | 56 | |
| 46 | 57 | func runEmailList(c *Ctx, args []string) int { |
internal/control/sig.go +10 −6
| @@ -18,14 +18,18 @@ import ( | ||
| 18 | 18 | |
| 19 | 19 | func init() { |
| 20 | 20 | register(Command{Path: []string{"pgp", "add"}, |
| 21 | Summary: "register an OpenPGP public key (armored)", | |
| 22 | Usage: "pgp add < key.asc", ReadsStdin: true, Run: runPGPAdd}) | |
| 21 | Summary: "register an OpenPGP public key (armored)", | |
| 22 | Usage: "pgp add < key.asc", | |
| 23 | Examples: []string{"pgp add < key.asc"}, | |
| 24 | ReadsStdin: true, Run: runPGPAdd}) | |
| 23 | 25 | register(Command{Path: []string{"pgp", "list"}, |
| 24 | Summary: "list registered OpenPGP keys", | |
| 25 | Usage: "pgp list", ReadOnly: true, Run: runPGPList}) | |
| 26 | Summary: "list registered OpenPGP keys", | |
| 27 | Usage: "pgp list", | |
| 28 | Examples: []string{"pgp list"}, ReadOnly: true, Run: runPGPList}) | |
| 26 | 29 | register(Command{Path: []string{"pgp", "remove"}, |
| 27 | Summary: "remove an OpenPGP key by fingerprint", | |
| 28 | Usage: "pgp remove <fingerprint>", Run: runPGPRemove}) | |
| 30 | Summary: "remove an OpenPGP key by fingerprint", | |
| 31 | Usage: "pgp remove <fingerprint>", | |
| 32 | Examples: []string{"pgp remove ABCD1234ABCD1234ABCD1234ABCD1234ABCD1234"}, Run: runPGPRemove}) | |
| 29 | 33 | register(Command{Path: []string{"repo", "commit"}, |
| 30 | 34 | Summary: "show one commit with its patch", |
| 31 | 35 | Usage: "repo commit <owner/name> <sha>", |
internal/control/teams.go +27 −18
| @@ -14,32 +14,41 @@ import ( | ||
| 14 | 14 | |
| 15 | 15 | func init() { |
| 16 | 16 | register(Command{Path: []string{"org", "team", "create"}, |
| 17 | Summary: "create a team", | |
| 18 | Usage: "org team create <org> <team>", Run: runTeamCreate}) | |
| 17 | Summary: "create a team", | |
| 18 | Usage: "org team create <org> <team>", | |
| 19 | Examples: []string{"org team create krz maintainers"}, Run: runTeamCreate}) | |
| 19 | 20 | register(Command{Path: []string{"org", "team", "delete"}, |
| 20 | Summary: "delete a team (its grants with it)", | |
| 21 | Usage: "org team delete <org> <team>", Run: runTeamDelete}) | |
| 21 | Summary: "delete a team (its grants with it)", | |
| 22 | Usage: "org team delete <org> <team>", | |
| 23 | Examples: []string{"org team delete krz maintainers"}, Run: runTeamDelete}) | |
| 22 | 24 | register(Command{Path: []string{"org", "team", "list"}, |
| 23 | Summary: "list an org's teams", | |
| 24 | Usage: "org team list <org>", ReadOnly: true, Run: runTeamList}) | |
| 25 | Summary: "list an org's teams", | |
| 26 | Usage: "org team list <org>", | |
| 27 | Examples: []string{"org team list krz"}, ReadOnly: true, Run: runTeamList}) | |
| 25 | 28 | register(Command{Path: []string{"org", "team", "show"}, |
| 26 | Summary: "show a team's members and grants", | |
| 27 | Usage: "org team show <org> <team>", ReadOnly: true, Run: runTeamShow}) | |
| 29 | Summary: "show a team's members and grants", | |
| 30 | Usage: "org team show <org> <team>", | |
| 31 | Examples: []string{"org team show krz maintainers"}, ReadOnly: true, Run: runTeamShow}) | |
| 28 | 32 | register(Command{Path: []string{"org", "team", "add"}, |
| 29 | Summary: "add org members to a team", | |
| 30 | Usage: "org team add <org> <team> <user>...", Run: runTeamAdd}) | |
| 33 | Summary: "add org members to a team", | |
| 34 | Usage: "org team add <org> <team> <user>...", | |
| 35 | Examples: []string{"org team add krz maintainers cmc"}, Run: runTeamAdd}) | |
| 31 | 36 | register(Command{Path: []string{"org", "team", "remove"}, |
| 32 | Summary: "remove members from a team", | |
| 33 | Usage: "org team remove <org> <team> <user>...", Run: runTeamRemove}) | |
| 37 | Summary: "remove members from a team", | |
| 38 | Usage: "org team remove <org> <team> <user>...", | |
| 39 | Examples: []string{"org team remove krz maintainers cmc"}, Run: runTeamRemove}) | |
| 34 | 40 | register(Command{Path: []string{"org", "team", "grant"}, |
| 35 | Summary: "grant a team a role on an org repo", | |
| 36 | Usage: "org team grant <org> <team> <owner/name> read|write|admin", Run: runTeamGrant}) | |
| 41 | Summary: "grant a team a role on an org repo", | |
| 42 | Usage: "org team grant <org> <team> <owner/name> read|write|admin", | |
| 43 | Examples: []string{"org team grant krz maintainers krz/gitbay write"}, Run: runTeamGrant}) | |
| 37 | 44 | register(Command{Path: []string{"org", "team", "revoke"}, |
| 38 | Summary: "revoke a team's grant", | |
| 39 | Usage: "org team revoke <org> <team> <owner/name>", Run: runTeamRevoke}) | |
| 45 | Summary: "revoke a team's grant", | |
| 46 | Usage: "org team revoke <org> <team> <owner/name>", | |
| 47 | Examples: []string{"org team revoke krz maintainers krz/gitbay"}, Run: runTeamRevoke}) | |
| 40 | 48 | register(Command{Path: []string{"org", "settings", "members-role"}, |
| 41 | Summary: "role plain membership implies on every org repo", | |
| 42 | Usage: "org settings members-role <org> write|read|none (default write)", Run: runOrgMembersRole}) | |
| 49 | Summary: "role plain membership implies on every org repo", | |
| 50 | Usage: "org settings members-role <org> write|read|none (default write)", | |
| 51 | Examples: []string{"org settings members-role krz read"}, Run: runOrgMembersRole}) | |
| 43 | 52 | } |
| 44 | 53 | |
| 45 | 54 | // orgAdminRef resolves an org and requires the caller to admin it. |
internal/control/theme.go +4 −2
| @@ -11,10 +11,12 @@ func init() { | ||
| 11 | 11 | register(Command{Path: []string{"web", "theme", "show"}, |
| 12 | 12 | Summary: "the colour scheme the web UI uses for you", |
| 13 | 13 | Usage: "web theme show", |
| 14 | Examples: []string{"web theme show"}, | |
| 14 | 15 | ReadOnly: true, Run: runWebThemeShow}) |
| 15 | 16 | register(Command{Path: []string{"web", "theme", "set"}, |
| 16 | Summary: "follow the browser's scheme, or force light or dark", | |
| 17 | Usage: "web theme set system|light|dark", Run: runWebThemeSet}) | |
| 17 | Summary: "follow the browser's scheme, or force light or dark", | |
| 18 | Usage: "web theme set system|light|dark", | |
| 19 | Examples: []string{"web theme set dark"}, Run: runWebThemeSet}) | |
| 18 | 20 | } |
| 19 | 21 | |
| 20 | 22 | // themes are the values the layout knows how to stamp. system is the |
internal/control/token.go +14 −6
| @@ -16,14 +16,22 @@ func init() { | ||
| 16 | 16 | register(Command{Path: []string{"token", "create"}, |
| 17 | 17 | Summary: "mint an API token (shown once)", |
| 18 | 18 | Usage: "token create --name <n> [--scope full|read] [--ttl 30d|720h]", |
| 19 | Run: runTokenCreate}) | |
| 19 | Flags: []Flag{ | |
| 20 | {"--name", "<n>", "the token's name", ""}, | |
| 21 | {"--scope", "full|read", "what the token may do", "full"}, | |
| 22 | {"--ttl", "30d|720h", "how long the token is valid", "never expires"}, | |
| 23 | }, | |
| 24 | Examples: []string{"token create --name laptop --scope read --ttl 30d"}, | |
| 25 | Run: runTokenCreate}) | |
| 20 | 26 | register(Command{Path: []string{"token", "list"}, |
| 21 | Summary: "list API tokens", | |
| 22 | Usage: "token list", ReadOnly: true, Run: runTokenList}) | |
| 27 | Summary: "list API tokens", | |
| 28 | Usage: "token list", | |
| 29 | Examples: []string{"token list"}, ReadOnly: true, Run: runTokenList}) | |
| 23 | 30 | register(Command{Path: []string{"token", "revoke"}, |
| 24 | Summary: "revoke an API token by name", | |
| 25 | Usage: "token revoke <name>", | |
| 26 | Run: runTokenRevoke}) | |
| 31 | Summary: "revoke an API token by name", | |
| 32 | Usage: "token revoke <name>", | |
| 33 | Examples: []string{"token revoke laptop"}, | |
| 34 | Run: runTokenRevoke}) | |
| 27 | 35 | } |
| 28 | 36 | |
| 29 | 37 | // parseTTL accepts Go durations plus a day suffix ("30d"). |
internal/control/web.go +11 −5
| @@ -14,14 +14,20 @@ func newStoredToken() (token, hash string, err error) { return store.NewToken() | ||
| 14 | 14 | |
| 15 | 15 | func init() { |
| 16 | 16 | register(Command{Path: []string{"web", "login"}, |
| 17 | Summary: "mint a one-time browser login URL", | |
| 18 | Usage: "web login", Run: runWebLogin}) | |
| 17 | Summary: "mint a one-time browser login URL", | |
| 18 | Usage: "web login", | |
| 19 | Examples: []string{"web login"}, Run: runWebLogin}) | |
| 19 | 20 | register(Command{Path: []string{"web", "sessions", "list"}, |
| 20 | Summary: "list your browser sessions", | |
| 21 | Usage: "web sessions list", ReadOnly: true, Run: runWebSessionsList}) | |
| 21 | Summary: "list your browser sessions", | |
| 22 | Usage: "web sessions list", | |
| 23 | Examples: []string{"web sessions list"}, ReadOnly: true, Run: runWebSessionsList}) | |
| 22 | 24 | register(Command{Path: []string{"web", "sessions", "revoke"}, |
| 23 | 25 | Summary: "end a browser session, or all of them", |
| 24 | Usage: "web sessions revoke <id>|--all", Run: runWebSessionsRevoke}) | |
| 26 | Usage: "web sessions revoke <id>|--all", | |
| 27 | Flags: []Flag{ | |
| 28 | {"--all", "", "revoke every browser session", ""}, | |
| 29 | }, | |
| 30 | Examples: []string{"web sessions revoke --all"}, Run: runWebSessionsRevoke}) | |
| 25 | 31 | } |
| 26 | 32 | |
| 27 | 33 | func runWebSessionsList(c *Ctx, args []string) int { |
internal/control/webhook.go +22 −8
| @@ -15,19 +15,33 @@ import ( | ||
| 15 | 15 | func init() { |
| 16 | 16 | register(Command{Path: []string{"webhook", "add"}, |
| 17 | 17 | Summary: "add a webhook", |
| 18 | Usage: "webhook add <owner/name> <url> [--secret <s>] [--events push,issue.created|*]", Run: runWebhookAdd}) | |
| 18 | Usage: "webhook add <owner/name> <url> [--secret <s>] [--events push,issue.created|*]", | |
| 19 | Flags: []Flag{ | |
| 20 | {"--secret", "<s>", "signs deliveries so the receiver can verify them", ""}, | |
| 21 | {"--events", "push,issue.created|*", "which events to send", "*"}, | |
| 22 | }, | |
| 23 | Examples: []string{"webhook add krz/gitbay https://ci.example.org/hook --events push"}, | |
| 24 | Run: runWebhookAdd}) | |
| 19 | 25 | register(Command{Path: []string{"webhook", "list"}, |
| 20 | Summary: "list webhooks", | |
| 21 | Usage: "webhook list <owner/name>", ReadOnly: true, Run: runWebhookList}) | |
| 26 | Summary: "list webhooks", | |
| 27 | Usage: "webhook list <owner/name>", | |
| 28 | Examples: []string{"webhook list krz/gitbay"}, ReadOnly: true, Run: runWebhookList}) | |
| 22 | 29 | register(Command{Path: []string{"webhook", "remove"}, |
| 23 | Summary: "remove a webhook", | |
| 24 | Usage: "webhook remove <owner/name> <id>", Run: runWebhookRemove}) | |
| 30 | Summary: "remove a webhook", | |
| 31 | Usage: "webhook remove <owner/name> <id>", | |
| 32 | Examples: []string{"webhook remove krz/gitbay 3"}, Run: runWebhookRemove}) | |
| 25 | 33 | register(Command{Path: []string{"webhook", "deliveries"}, |
| 26 | 34 | Summary: "recent deliveries", |
| 27 | Usage: "webhook deliveries <owner/name> [--limit n]", ReadOnly: true, Run: runWebhookDeliveries}) | |
| 35 | Usage: "webhook deliveries <owner/name> [--limit n]", | |
| 36 | Flags: []Flag{ | |
| 37 | {"--limit", "n", "rows to show", "20"}, | |
| 38 | }, | |
| 39 | Examples: []string{"webhook deliveries krz/gitbay --limit 50"}, | |
| 40 | ReadOnly: true, Run: runWebhookDeliveries}) | |
| 28 | 41 | register(Command{Path: []string{"webhook", "redeliver"}, |
| 29 | Summary: "queue a delivery again", | |
| 30 | Usage: "webhook redeliver <owner/name> <delivery-id>", Run: runWebhookRedeliver}) | |
| 42 | Summary: "queue a delivery again", | |
| 43 | Usage: "webhook redeliver <owner/name> <delivery-id>", | |
| 44 | Examples: []string{"webhook redeliver krz/gitbay 42"}, Run: runWebhookRedeliver}) | |
| 31 | 45 | } |
| 32 | 46 | |
| 33 | 47 | func runWebhookAdd(c *Ctx, args []string) int { |