Commit b347d6c8c4
Verified · cmc ci/build: success ci/test: success
.gitbay/wiki/Users.org +3 −2
| @@ -632,8 +632,9 @@ addresses receive anything. Delivery retries on relay failure. | ||
| 632 | 632 | Every read command takes =--json= and emits one envelope: |
| 633 | 633 | ={"protocol_version": 1, "data": ...}=. stdout is data, stderr is |
| 634 | 634 | messages. Exit codes are stable: 0 ok, 1 failure, 2 usage, 3 not found, |
| 635 | 4 denied, 5 server/protocol error. Nothing ever prompts; destructive | |
| 636 | commands take =--yes=. | |
| 635 | 4 denied, 5 server/protocol error. Usage means the arguments were wrong; | |
| 636 | a refusal — a name already taken, a state that does not allow the change | |
| 637 | — is a failure. Nothing ever prompts; destructive commands take =--yes=. | |
| 637 | 638 | |
| 638 | 639 | For HTTP automation see [[API]]. |
| 639 | 640 | |
e2e/adminusers_test.go +3 −3
| @@ -190,7 +190,7 @@ func TestAdminPromoteDemote(t *testing.T) { | ||
| 190 | 190 | t.Fatalf("disabled account promoted: exit %d %s", code, errOut) |
| 191 | 191 | } |
| 192 | 192 | // The only admin cannot step down. |
| 193 | if _, errOut, code := inst.ssh(t, rootKey, "", "admin", "user", "demote", "root"); code != 2 || !strings.Contains(errOut, "only instance admin") { | |
| 193 | if _, errOut, code := inst.ssh(t, rootKey, "", "admin", "user", "demote", "root"); code != 1 || !strings.Contains(errOut, "only instance admin") { | |
| 194 | 194 | t.Fatalf("last admin demoted: exit %d %s", code, errOut) |
| 195 | 195 | } |
| 196 | 196 | if _, _, code := inst.ssh(t, rootKey, "", "admin", "user", "promote", "alice"); code != 0 { |
| @@ -209,7 +209,7 @@ func TestAdminPromoteDemote(t *testing.T) { | ||
| 209 | 209 | if _, _, code := inst.ssh(t, rootKey, "", "audit"); code != 4 { |
| 210 | 210 | t.Fatal("demoted account still admin") |
| 211 | 211 | } |
| 212 | if _, _, code := inst.ssh(t, aliceKey, "", "admin", "user", "demote", "alice"); code != 2 { | |
| 212 | if _, _, code := inst.ssh(t, aliceKey, "", "admin", "user", "demote", "alice"); code != 1 { | |
| 213 | 213 | t.Fatal("last admin demoted") |
| 214 | 214 | } |
| 215 | 215 | // Host-local recovery: the operator restores root without an admin key. |
| @@ -356,7 +356,7 @@ func TestAdminHostAndSSHAreOneSurface(t *testing.T) { | ||
| 356 | 356 | if _, _, code := inst.ssh(t, carolKey, "", "whoami"); code != 0 { |
| 357 | 357 | t.Fatal("created account cannot authenticate") |
| 358 | 358 | } |
| 359 | if _, errOut, code := inst.ssh(t, rootKey, "", "admin", "user", "create", "alice"); code != 2 || !strings.Contains(errOut, "taken") { | |
| 359 | if _, errOut, code := inst.ssh(t, rootKey, "", "admin", "user", "create", "alice"); code != 1 || !strings.Contains(errOut, "taken") { | |
| 360 | 360 | t.Fatalf("duplicate create: exit %d %s", code, errOut) |
| 361 | 361 | } |
| 362 | 362 | for _, args := range [][]string{{"admin", "stats"}, {"admin", "user", "disable", "carol"}, {"admin", "invite", "--email", "x@example.test"}} { |
e2e/diffcomment_test.go +1 −1
| @@ -76,7 +76,7 @@ func TestDiffComments(t *testing.T) { | ||
| 76 | 76 | } |
| 77 | 77 | json.Unmarshal([]byte(out), &env3) |
| 78 | 78 | if _, errOut, code = inst.ssh(t, bobKey, "", "mr", "diff-comment", "alice/lib", "1", |
| 79 | "--reply", fmt.Sprint(env3.Data.ID), "--message", "'nested'"); code != 2 || !strings.Contains(errOut, "thread root") { | |
| 79 | "--reply", fmt.Sprint(env3.Data.ID), "--message", "'nested'"); code != 1 || !strings.Contains(errOut, "thread root") { | |
| 80 | 80 | t.Fatalf("nested reply: exit %d, %s", code, errOut) |
| 81 | 81 | } |
| 82 | 82 | |
e2e/org_test.go +2 −2
| @@ -97,7 +97,7 @@ func TestOrganizations(t *testing.T) { | ||
| 97 | 97 | t.Fatalf("bob (now admin) removing alice: %s", errOut) |
| 98 | 98 | } |
| 99 | 99 | _, errOut, code := inst.ssh(t, bobKey, "", "org", "members", "remove", "krz", "bob") |
| 100 | if code != 2 || !strings.Contains(errOut, "at least one admin") { | |
| 100 | if code != 1 || !strings.Contains(errOut, "at least one admin") { | |
| 101 | 101 | t.Fatalf("last admin removal: %d %s", code, errOut) |
| 102 | 102 | } |
| 103 | 103 | |
| @@ -150,7 +150,7 @@ func TestOrganizations(t *testing.T) { | ||
| 150 | 150 | } |
| 151 | 151 | rnWork := t.TempDir() |
| 152 | 152 | mustGit(t, rnWork, inst.gitEnv(aliceKey), "clone", inst.sshURL("oldname/thing"), "w1") |
| 153 | if _, errOut, code = inst.ssh(t, aliceKey, "", "org", "rename", "oldname", "bob"); code != 2 || !strings.Contains(errOut, "taken") { | |
| 153 | if _, errOut, code = inst.ssh(t, aliceKey, "", "org", "rename", "oldname", "bob"); code != 1 || !strings.Contains(errOut, "taken") { | |
| 154 | 154 | t.Fatalf("rename onto user name: %d %s", code, errOut) |
| 155 | 155 | } |
| 156 | 156 | if _, errOut, code = inst.ssh(t, aliceKey, "", "org", "rename", "oldname", "newname"); code != 0 { |
e2e/release_test.go +2 −2
| @@ -36,7 +36,7 @@ func TestReleases(t *testing.T) { | ||
| 36 | 36 | "--title", "'First light'", "--notes", "'the **first** release'"); code != 0 { |
| 37 | 37 | t.Fatalf("release create: %s", errOut) |
| 38 | 38 | } |
| 39 | if _, _, code := inst.ssh(t, aliceKey, "", "release", "create", "alice/app", "v1.0"); code != 2 { | |
| 39 | if _, _, code := inst.ssh(t, aliceKey, "", "release", "create", "alice/app", "v1.0"); code != 1 { | |
| 40 | 40 | t.Fatal("duplicate release accepted") |
| 41 | 41 | } |
| 42 | 42 | |
| @@ -67,7 +67,7 @@ func TestReleases(t *testing.T) { | ||
| 67 | 67 | if _, errOut, code := inst.ssh(t, aliceKey, payload, "release", "asset", "add", "alice/app", "v1.0", "tool-linux-amd64"); code != 0 { |
| 68 | 68 | t.Fatalf("asset add: %s", errOut) |
| 69 | 69 | } |
| 70 | if _, _, code := inst.ssh(t, aliceKey, payload, "release", "asset", "add", "alice/app", "v1.0", "tool-linux-amd64"); code != 2 { | |
| 70 | if _, _, code := inst.ssh(t, aliceKey, payload, "release", "asset", "add", "alice/app", "v1.0", "tool-linux-amd64"); code != 1 { | |
| 71 | 71 | t.Fatal("duplicate asset accepted") |
| 72 | 72 | } |
| 73 | 73 | if _, errOut, code := inst.ssh(t, aliceKey, "", "release", "asset", "add", "alice/app", "v1.0", "empty-file"); code != 2 || !strings.Contains(errOut, "empty asset") { |
e2e/ssh_test.go +2 −2
| @@ -255,8 +255,8 @@ func TestControlPlaneOverBareSSH(t *testing.T) { | ||
| 255 | 255 | inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub") |
| 256 | 256 | alicePub, _ := os.ReadFile(aliceKey + ".pub") |
| 257 | 257 | _, errOut, code = inst.ssh(t, bobKey, string(alicePub), "keys", "add") |
| 258 | if code != 2 { | |
| 259 | t.Fatalf("duplicate key add: exit %d, want 2", code) | |
| 258 | if code != 1 { | |
| 259 | t.Fatalf("duplicate key add: exit %d, want 1", code) | |
| 260 | 260 | } |
| 261 | 261 | want := "that key is already registered to another account; remove it there first or use a different key" |
| 262 | 262 | if !strings.Contains(errOut, want) { |
internal/control/adminhost.go +1 −1
| @@ -72,7 +72,7 @@ func runAdminUserCreate(c *Ctx, args []string) int { | ||
| 72 | 72 | return c.fail(protocol.ExitUsage, usage) |
| 73 | 73 | } |
| 74 | 74 | if err := policy.ValidateOwnerName(username); err != nil { |
| 75 | return c.failErr(err) | |
| 75 | return c.failInput(err) | |
| 76 | 76 | } |
| 77 | 77 | // Parse the key before creating anything, so a bad key leaves no |
| 78 | 78 | // half-made account behind. |
internal/control/control.go +16 −6
| @@ -223,13 +223,23 @@ func (c *Ctx) emit(data any, plain func(w io.Writer)) int { | ||
| 223 | 223 | return protocol.ExitOK |
| 224 | 224 | } |
| 225 | 225 | |
| 226 | // failErr reports an error from a store or helper call with the exit code | |
| 227 | // its kind deserves: not-found is not-found, the database or I/O failing | |
| 228 | // is a failure, and anything else is the caller's mistake, which is what | |
| 229 | // most such errors are (a name that does not validate, a state that does | |
| 230 | // not allow the change). A SQLite I/O error used to be a usage error and | |
| 231 | // an HTTP 400 (#107). | |
| 226 | // failErr reports an error from a store call: not-found is not-found, | |
| 227 | // and anything else — the database failing, a duplicate, a state that | |
| 228 | // does not allow the change — is a failure. An error about the caller's | |
| 229 | // own arguments goes through failInput instead; this used to default to | |
| 230 | // usage, which turned every refusal into exit 2 (#211). | |
| 232 | 231 | func (c *Ctx) failErr(err error) int { |
| 232 | if errors.Is(err, store.ErrNotFound) { | |
| 233 | return c.fail(protocol.ExitNotFound, "%v", err) | |
| 234 | } | |
| 235 | return c.fail(protocol.ExitFailure, "%v", err) | |
| 236 | } | |
| 237 | ||
| 238 | // failInput reports an error about the caller's input — a name that does | |
| 239 | // not validate, a flag value out of range, a body that could not be read | |
| 240 | // — as a usage error, unless the database or I/O failed underneath it. | |
| 241 | // A SQLite I/O error used to be a usage error and an HTTP 400 (#107). | |
| 242 | func (c *Ctx) failInput(err error) int { | |
| 233 | 243 | switch { |
| 234 | 244 | case errors.Is(err, store.ErrNotFound): |
| 235 | 245 | return c.fail(protocol.ExitNotFound, "%v", err) |
internal/control/control_test.go +24 −17
| @@ -227,23 +227,30 @@ func TestRefusalsHonourJSON(t *testing.T) { | ||
| 227 | 227 | } |
| 228 | 228 | } |
| 229 | 229 | |
| 230 | // TestFailErrExitCodes: not-found, an internal failure, and the caller's | |
| 231 | // mistake each get their own exit code (#107). | |
| 230 | // TestFailErrExitCodes: a store error is not-found or a failure, never | |
| 231 | // usage (#211); an input error is usage unless the I/O beneath it failed | |
| 232 | // (#107). | |
| 232 | 233 | func TestFailErrExitCodes(t *testing.T) { |
| 233 | code := func(err error) int { | |
| 234 | c := &Ctx{Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}} | |
| 235 | return c.failErr(err) | |
| 236 | } | |
| 237 | if got := code(store.ErrNotFound); got != protocol.ExitNotFound { | |
| 238 | t.Errorf("not found: %d", got) | |
| 239 | } | |
| 240 | if got := code(fmt.Errorf("looking up: %w", store.ErrNotFound)); got != protocol.ExitNotFound { | |
| 241 | t.Errorf("wrapped not found: %d", got) | |
| 242 | } | |
| 243 | if got := code(errors.New("name must be lowercase")); got != protocol.ExitUsage { | |
| 244 | t.Errorf("caller's mistake: %d", got) | |
| 245 | } | |
| 246 | if got := code(&fs.PathError{Op: "open", Path: "/x", Err: fs.ErrPermission}); got != protocol.ExitFailure { | |
| 247 | t.Errorf("i/o failure: %d", got) | |
| 234 | ctx := func() *Ctx { return &Ctx{Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}} } | |
| 235 | notFound := fmt.Errorf("looking up: %w", store.ErrNotFound) | |
| 236 | refused := errors.New("the name is taken") | |
| 237 | ioErr := &fs.PathError{Op: "open", Path: "/x", Err: fs.ErrPermission} | |
| 238 | for _, tc := range []struct { | |
| 239 | name string | |
| 240 | fn func(*Ctx, error) int | |
| 241 | err error | |
| 242 | want int | |
| 243 | }{ | |
| 244 | {"failErr not found", (*Ctx).failErr, store.ErrNotFound, protocol.ExitNotFound}, | |
| 245 | {"failErr wrapped not found", (*Ctx).failErr, notFound, protocol.ExitNotFound}, | |
| 246 | {"failErr refusal", (*Ctx).failErr, refused, protocol.ExitFailure}, | |
| 247 | {"failErr i/o", (*Ctx).failErr, ioErr, protocol.ExitFailure}, | |
| 248 | {"failInput not found", (*Ctx).failInput, notFound, protocol.ExitNotFound}, | |
| 249 | {"failInput caller's mistake", (*Ctx).failInput, errors.New("name must be lowercase"), protocol.ExitUsage}, | |
| 250 | {"failInput i/o", (*Ctx).failInput, ioErr, protocol.ExitFailure}, | |
| 251 | } { | |
| 252 | if got := tc.fn(ctx(), tc.err); got != tc.want { | |
| 253 | t.Errorf("%s: exit %d, want %d", tc.name, got, tc.want) | |
| 254 | } | |
| 248 | 255 | } |
| 249 | 256 | } |
internal/control/diffcomment.go +1 −1
| @@ -66,7 +66,7 @@ func runDiffComment(c *Ctx, args []string) int { | ||
| 66 | 66 | } |
| 67 | 67 | body, err := bodyFrom(c, message, file) |
| 68 | 68 | if err != nil { |
| 69 | return c.failErr(err) | |
| 69 | return c.failInput(err) | |
| 70 | 70 | } |
| 71 | 71 | if strings.TrimSpace(body) == "" { |
| 72 | 72 | return c.fail(protocol.ExitUsage, "empty comment; use --message or --file -") |
internal/control/import.go +1 −1
| @@ -47,7 +47,7 @@ func runRepoImport(c *Ctx, args []string) int { | ||
| 47 | 47 | return c.fail(protocol.ExitUsage, "usage: repo import <owner/name> --from <url>") |
| 48 | 48 | } |
| 49 | 49 | if err := policy.ValidateName(name); err != nil { |
| 50 | return c.failErr(err) | |
| 50 | return c.failInput(err) | |
| 51 | 51 | } |
| 52 | 52 | // Same ownership rule as repo create: yourself, or an org you admin. |
| 53 | 53 | ownerKind, ownerID := "user", c.User.ID |
internal/control/issue.go +7 −7
| @@ -127,7 +127,7 @@ func runIssueCreate(c *Ctx, args []string) int { | ||
| 127 | 127 | } |
| 128 | 128 | fmtName, err := markupFormat(format) |
| 129 | 129 | if err != nil { |
| 130 | return c.failErr(err) | |
| 130 | return c.failInput(err) | |
| 131 | 131 | } |
| 132 | 132 | if fmtName == "" { |
| 133 | 133 | fmtName = "md" |
| @@ -142,7 +142,7 @@ func runIssueCreate(c *Ctx, args []string) int { | ||
| 142 | 142 | } |
| 143 | 143 | b, err := bodyFrom(c, body, file) |
| 144 | 144 | if err != nil { |
| 145 | return c.failErr(err) | |
| 145 | return c.failInput(err) | |
| 146 | 146 | } |
| 147 | 147 | n, err := c.Store.CreateIssue(repo.ID, c.User.ID, title, b, fmtName) |
| 148 | 148 | if err != nil { |
| @@ -182,7 +182,7 @@ func runIssueList(c *Ctx, args []string) int { | ||
| 182 | 182 | f.Search = fl.Value("--search") |
| 183 | 183 | if fl.Has("--search") { |
| 184 | 184 | if err := validQuery(f.Search); err != nil { |
| 185 | return c.failErr(err) | |
| 185 | return c.failInput(err) | |
| 186 | 186 | } |
| 187 | 187 | } |
| 188 | 188 | if path == "" || (f.State != "open" && f.State != "closed" && f.State != "all") { |
| @@ -303,13 +303,13 @@ func editText(c *Ctx, args []string, kind string) (rest []string, title, body, f | ||
| 303 | 303 | if file != "" { |
| 304 | 304 | b, err := bodyFrom(c, "", file) |
| 305 | 305 | if err != nil { |
| 306 | return nil, nil, nil, nil, c.failErr(err) | |
| 306 | return nil, nil, nil, nil, c.failInput(err) | |
| 307 | 307 | } |
| 308 | 308 | bodyV, haveBody = b, true |
| 309 | 309 | } |
| 310 | 310 | fmtName, err := markupFormat(formatV) |
| 311 | 311 | if err != nil { |
| 312 | return nil, nil, nil, nil, c.failErr(err) | |
| 312 | return nil, nil, nil, nil, c.failInput(err) | |
| 313 | 313 | } |
| 314 | 314 | if !haveTitle && !haveBody && fmtName == "" { |
| 315 | 315 | return nil, nil, nil, nil, c.fail(protocol.ExitUsage, "usage: %s edit <owner/name> <n> [--title <t>] [--body <b> | --file -] [--format md|org]", kind) |
| @@ -375,7 +375,7 @@ func addRemoveFlags(args []string) (rest, adds, removes []string, err error) { | ||
| 375 | 375 | func runIssueLabel(c *Ctx, args []string) int { |
| 376 | 376 | rest, adds, removes, err := addRemoveFlags(args) |
| 377 | 377 | if err != nil { |
| 378 | return c.failErr(err) | |
| 378 | return c.failInput(err) | |
| 379 | 379 | } |
| 380 | 380 | if len(adds)+len(removes) == 0 { |
| 381 | 381 | return c.fail(protocol.ExitUsage, "usage: issue label <owner/name> <n> [--add <l>]... [--remove <l>]...") |
| @@ -414,7 +414,7 @@ func runIssueLabel(c *Ctx, args []string) int { | ||
| 414 | 414 | func runIssueAssign(c *Ctx, args []string) int { |
| 415 | 415 | rest, adds, removes, err := addRemoveFlags(args) |
| 416 | 416 | if err != nil { |
| 417 | return c.failErr(err) | |
| 417 | return c.failInput(err) | |
| 418 | 418 | } |
| 419 | 419 | if len(adds)+len(removes) == 0 { |
| 420 | 420 | return c.fail(protocol.ExitUsage, "usage: issue assign <owner/name> <n> [--add <user>]... [--remove <user>]...") |
internal/control/milestone.go +1 −3
| @@ -64,9 +64,7 @@ func runMilestoneCreate(c *Ctx, args []string) int { | ||
| 64 | 64 | if errors.Is(err, store.ErrOrgScoped) { |
| 65 | 65 | return c.fail(protocol.ExitFailure, "%s", orgScopedMsg(repo, "milestone", title, "create")) |
| 66 | 66 | } |
| 67 | // A duplicate title is a failure, not a usage error, which is what | |
| 68 | // failErr would make of it; org milestone create answers the same. | |
| 69 | return c.fail(protocol.ExitFailure, "%v", err) | |
| 67 | return c.failErr(err) | |
| 70 | 68 | } |
| 71 | 69 | return c.emit(map[string]string{"milestone": title}, func(w io.Writer) { |
| 72 | 70 | fmt.Fprintf(w, "created milestone %q on %s\n", title, repo.Path()) |
internal/control/mirrorcmd.go +1 −1
| @@ -44,7 +44,7 @@ func runMirrorAdd(c *Ctx, args []string) int { | ||
| 44 | 44 | // The worker's git process dials this URL from the server: same SSRF |
| 45 | 45 | // surface as a webhook target, same rules. |
| 46 | 46 | if err := webhook.ValidateURL(urlArg, c.Cfg.Webhooks.AllowLocal); err != nil { |
| 47 | return c.failErr(err) | |
| 47 | return c.failInput(err) | |
| 48 | 48 | } |
| 49 | 49 | repo, code := resolveRepo(c, path, policy.CanAdmin) |
| 50 | 50 | if code >= 0 { |
internal/control/mr.go +5 −5
| @@ -114,7 +114,7 @@ func runRepoFork(c *Ctx, args []string) int { | ||
| 114 | 114 | name = src.Name |
| 115 | 115 | } |
| 116 | 116 | if err := policy.ValidateName(name); err != nil { |
| 117 | return c.failErr(err) | |
| 117 | return c.failInput(err) | |
| 118 | 118 | } |
| 119 | 119 | repoCreateMu.Lock() |
| 120 | 120 | if code := checkRepoQuota(c); code >= 0 { |
| @@ -286,7 +286,7 @@ func runMRCreate(c *Ctx, args []string) int { | ||
| 286 | 286 | } |
| 287 | 287 | fmtName, err := markupFormat(format) |
| 288 | 288 | if err != nil { |
| 289 | return c.failErr(err) | |
| 289 | return c.failInput(err) | |
| 290 | 290 | } |
| 291 | 291 | if fmtName == "" { |
| 292 | 292 | fmtName = "md" |
| @@ -323,7 +323,7 @@ func runMRCreate(c *Ctx, args []string) int { | ||
| 323 | 323 | } |
| 324 | 324 | b, err := bodyFrom(c, body, file) |
| 325 | 325 | if err != nil { |
| 326 | return c.failErr(err) | |
| 326 | return c.failInput(err) | |
| 327 | 327 | } |
| 328 | 328 | n, err := c.Store.CreateMR(repo.ID, c.User.ID, srcRepo.ID, srcBranch, target, title, b, headSHA, fmtName, f.Has("--draft")) |
| 329 | 329 | if err != nil { |
| @@ -455,7 +455,7 @@ func runMRList(c *Ctx, args []string) int { | ||
| 455 | 455 | f.Search = fl.Value("--search") |
| 456 | 456 | if fl.Has("--search") { |
| 457 | 457 | if err := validQuery(f.Search); err != nil { |
| 458 | return c.failErr(err) | |
| 458 | return c.failInput(err) | |
| 459 | 459 | } |
| 460 | 460 | } |
| 461 | 461 | valid := map[string]bool{"open": true, "merged": true, "closed": true, "source_gone": true, "all": true} |
| @@ -845,7 +845,7 @@ func runMRReview(c *Ctx, args []string) int { | ||
| 845 | 845 | func runMRReviewRequest(c *Ctx, args []string) int { |
| 846 | 846 | rest, adds, removes, err := addRemoveFlags(args) |
| 847 | 847 | if err != nil { |
| 848 | return c.failErr(err) | |
| 848 | return c.failInput(err) | |
| 849 | 849 | } |
| 850 | 850 | if len(adds)+len(removes) == 0 { |
| 851 | 851 | return c.fail(protocol.ExitUsage, "usage: mr review request <owner/name> <n> [--add <user>]... [--remove <user>]...") |
internal/control/org.go +2 −2
| @@ -63,7 +63,7 @@ func runOrgCreate(c *Ctx, args []string) int { | ||
| 63 | 63 | return c.fail(protocol.ExitUsage, "usage: org create <name>") |
| 64 | 64 | } |
| 65 | 65 | if err := policy.ValidateOwnerName(args[0]); err != nil { |
| 66 | return c.failErr(err) | |
| 66 | return c.failInput(err) | |
| 67 | 67 | } |
| 68 | 68 | if _, err := c.Store.CreateOrg(args[0], c.User.ID); err != nil { |
| 69 | 69 | return c.fail(protocol.ExitFailure, "%v", err) |
| @@ -138,7 +138,7 @@ func runOrgRename(c *Ctx, args []string) int { | ||
| 138 | 138 | } |
| 139 | 139 | newName := args[1] |
| 140 | 140 | if err := policy.ValidateOwnerName(newName); err != nil { |
| 141 | return c.failErr(err) | |
| 141 | return c.failInput(err) | |
| 142 | 142 | } |
| 143 | 143 | oldDir := filepath.Join(c.Cfg.Server.Root, "repos", org.Name) |
| 144 | 144 | newDir := filepath.Join(c.Cfg.Server.Root, "repos", newName) |
internal/control/pagescmd.go +1 −1
| @@ -74,7 +74,7 @@ func runDomainAdd(c *Ctx, args []string) int { | ||
| 74 | 74 | } |
| 75 | 75 | domain := strings.ToLower(args[1]) |
| 76 | 76 | if err := validatePageDomain(c, domain); err != nil { |
| 77 | return c.failErr(err) | |
| 77 | return c.failInput(err) | |
| 78 | 78 | } |
| 79 | 79 | repo, code := resolveRepo(c, args[0], policy.CanAdmin) |
| 80 | 80 | if code >= 0 { |
internal/control/profile.go +4 −4
| @@ -347,7 +347,7 @@ func runProfileShow(c *Ctx, args []string) int { | ||
| 347 | 347 | func runProfileSet(c *Ctx, args []string) int { |
| 348 | 348 | rest, e, err := parseProfileFlags(c, args) |
| 349 | 349 | if err != nil { |
| 350 | return c.failErr(err) | |
| 350 | return c.failInput(err) | |
| 351 | 351 | } |
| 352 | 352 | if len(rest) != 0 { |
| 353 | 353 | return c.fail(protocol.ExitUsage, |
| @@ -362,7 +362,7 @@ func runProfileSet(c *Ctx, args []string) int { | ||
| 362 | 362 | } |
| 363 | 363 | p, err = applyProfile(p, e) |
| 364 | 364 | if err != nil { |
| 365 | return c.failErr(err) | |
| 365 | return c.failInput(err) | |
| 366 | 366 | } |
| 367 | 367 | if err := c.Store.SetOwnerProfile("user", c.User.ID, p); err != nil { |
| 368 | 368 | return c.fail(protocol.ExitFailure, "%v", err) |
| @@ -375,7 +375,7 @@ func runProfileSet(c *Ctx, args []string) int { | ||
| 375 | 375 | func runOrgProfile(c *Ctx, args []string) int { |
| 376 | 376 | rest, e, err := parseProfileFlags(c, args) |
| 377 | 377 | if err != nil { |
| 378 | return c.failErr(err) | |
| 378 | return c.failInput(err) | |
| 379 | 379 | } |
| 380 | 380 | if len(rest) != 1 { |
| 381 | 381 | return c.fail(protocol.ExitUsage, |
| @@ -395,7 +395,7 @@ func runOrgProfile(c *Ctx, args []string) int { | ||
| 395 | 395 | } |
| 396 | 396 | p, err = applyProfile(p, e) |
| 397 | 397 | if err != nil { |
| 398 | return c.failErr(err) | |
| 398 | return c.failInput(err) | |
| 399 | 399 | } |
| 400 | 400 | if err := c.Store.SetOwnerProfile("org", org.ID, p); err != nil { |
| 401 | 401 | return c.fail(protocol.ExitFailure, "%v", err) |
internal/control/release.go +4 −4
| @@ -88,7 +88,7 @@ func runReleaseCreate(c *Ctx, args []string) int { | ||
| 88 | 88 | } |
| 89 | 89 | fmtName, err := markupFormat(format) |
| 90 | 90 | if err != nil { |
| 91 | return c.failErr(err) | |
| 91 | return c.failInput(err) | |
| 92 | 92 | } |
| 93 | 93 | if fmtName == "" { |
| 94 | 94 | fmtName = "md" |
| @@ -106,7 +106,7 @@ func runReleaseCreate(c *Ctx, args []string) int { | ||
| 106 | 106 | } |
| 107 | 107 | body, err := bodyFrom(c, notes, file) |
| 108 | 108 | if err != nil { |
| 109 | return c.failErr(err) | |
| 109 | return c.failInput(err) | |
| 110 | 110 | } |
| 111 | 111 | if title == "" { |
| 112 | 112 | title = tag |
| @@ -159,7 +159,7 @@ func runReleaseEdit(c *Ctx, args []string) int { | ||
| 159 | 159 | setTitle, setNotes := f.Has("--title"), f.Has("--notes") || f.Has("--file") |
| 160 | 160 | fmtName, err := markupFormat(format) |
| 161 | 161 | if err != nil { |
| 162 | return c.failErr(err) | |
| 162 | return c.failInput(err) | |
| 163 | 163 | } |
| 164 | 164 | if path == "" || tag == "" || (!setTitle && !setNotes && fmtName == "") { |
| 165 | 165 | return c.fail(protocol.ExitUsage, usage) |
| @@ -184,7 +184,7 @@ func runReleaseEdit(c *Ctx, args []string) int { | ||
| 184 | 184 | body := rel.Notes |
| 185 | 185 | if setNotes { |
| 186 | 186 | if body, err = bodyFrom(c, notes, file); err != nil { |
| 187 | return c.failErr(err) | |
| 187 | return c.failInput(err) | |
| 188 | 188 | } |
| 189 | 189 | } |
| 190 | 190 | if fmtName == "" { |
internal/control/repo.go +6 −6
| @@ -184,7 +184,7 @@ func runRepoCreate(c *Ctx, args []string) int { | ||
| 184 | 184 | return c.fail(protocol.ExitUsage, "usage: repo create <owner/name> [--private]") |
| 185 | 185 | } |
| 186 | 186 | if err := policyValidateRepoName(name); err != nil { |
| 187 | return c.failErr(err) | |
| 187 | return c.failInput(err) | |
| 188 | 188 | } |
| 189 | 189 | ownerKind, ownerID := "user", c.User.ID |
| 190 | 190 | if owner != c.User.Username { |
| @@ -461,7 +461,7 @@ func runRepoRename(c *Ctx, args []string) int { | ||
| 461 | 461 | return c.fail(protocol.ExitUsage, "%s is already named %s", repo.Path(), newName) |
| 462 | 462 | } |
| 463 | 463 | if err := policyValidateRepoName(newName); err != nil { |
| 464 | return c.failErr(err) | |
| 464 | return c.failInput(err) | |
| 465 | 465 | } |
| 466 | 466 | oldDir := RepoDir(c.Cfg.Server.Root, repo.OwnerName, repo.Name) |
| 467 | 467 | newDir := RepoDir(c.Cfg.Server.Root, repo.OwnerName, newName) |
| @@ -666,7 +666,7 @@ func runSetWebsite(c *Ctx, args []string) int { | ||
| 666 | 666 | } |
| 667 | 667 | site := strings.TrimSpace(args[1]) |
| 668 | 668 | if err := validateWebsite(site); err != nil { |
| 669 | return c.failErr(err) | |
| 669 | return c.failInput(err) | |
| 670 | 670 | } |
| 671 | 671 | if len(site) > 256 { |
| 672 | 672 | return c.fail(protocol.ExitUsage, "website URL too long (max 256)") |
| @@ -816,7 +816,7 @@ func editTopics(c *Ctx, args []string, add bool) int { | ||
| 816 | 816 | if add { |
| 817 | 817 | for _, t := range topics { |
| 818 | 818 | if err := policy.ValidateTopic(t); err != nil { |
| 819 | return c.failErr(err) | |
| 819 | return c.failInput(err) | |
| 820 | 820 | } |
| 821 | 821 | } |
| 822 | 822 | have, err := c.Store.ListTopics(repo.ID) |
| @@ -863,7 +863,7 @@ func runRepoSearch(c *Ctx, args []string) int { | ||
| 863 | 863 | return c.fail(protocol.ExitUsage, "usage: repo search <query>") |
| 864 | 864 | } |
| 865 | 865 | if err := validQuery(args[0]); err != nil { |
| 866 | return c.failErr(err) | |
| 866 | return c.failInput(err) | |
| 867 | 867 | } |
| 868 | 868 | q := strings.ToLower(args[0]) |
| 869 | 869 | |
| @@ -930,7 +930,7 @@ func runRepoGrep(c *Ctx, args []string) int { | ||
| 930 | 930 | return c.fail(protocol.ExitUsage, "usage: repo grep <owner/name> <query> [--ref <ref>]") |
| 931 | 931 | } |
| 932 | 932 | if err := validQuery(query); err != nil { |
| 933 | return c.failErr(err) | |
| 933 | return c.failInput(err) | |
| 934 | 934 | } |
| 935 | 935 | repo, code := resolveRepo(c, path, policy.CanRead) |
| 936 | 936 | if code >= 0 { |
internal/control/search.go +1 −1
| @@ -86,7 +86,7 @@ func runSearch(c *Ctx, args []string) int { | ||
| 86 | 86 | return c.fail(protocol.ExitUsage, "usage: %s", usage) |
| 87 | 87 | } |
| 88 | 88 | if err := validQuery(f.Pos[0]); err != nil { |
| 89 | return c.failErr(err) | |
| 89 | return c.failInput(err) | |
| 90 | 90 | } |
| 91 | 91 | kinds := f.List("--kind") |
| 92 | 92 | for _, k := range kinds { |
internal/control/sig.go +1 −1
| @@ -45,7 +45,7 @@ func runPGPAdd(c *Ctx, args []string) int { | ||
| 45 | 45 | } |
| 46 | 46 | meta, err := sig.ParsePGPKey(raw) |
| 47 | 47 | if err != nil { |
| 48 | return c.failErr(err) | |
| 48 | return c.failInput(err) | |
| 49 | 49 | } |
| 50 | 50 | uids, _ := json.Marshal(meta.Emails) |
| 51 | 51 | if err := c.Store.AddPGPKey(c.User.ID, meta.Fingerprint, string(raw), string(uids), meta.ExpiresAt, meta.RevokedAt); err != nil { |
internal/control/teams.go +1 −1
| @@ -96,7 +96,7 @@ func runTeamCreate(c *Ctx, args []string) int { | ||
| 96 | 96 | return code |
| 97 | 97 | } |
| 98 | 98 | if err := policy.ValidateName(args[1]); err != nil { |
| 99 | return c.failErr(err) | |
| 99 | return c.failInput(err) | |
| 100 | 100 | } |
| 101 | 101 | if _, err := c.Store.CreateTeam(org.ID, args[1]); err != nil { |
| 102 | 102 | return c.failErr(err) |
internal/control/thread.go +2 −2
| @@ -83,7 +83,7 @@ func runComment(c *Ctx, args []string, t thread, noun string, | ||
| 83 | 83 | } |
| 84 | 84 | fmtName, err := markupFormat(f.Value("--format")) |
| 85 | 85 | if err != nil { |
| 86 | return c.failErr(err) | |
| 86 | return c.failInput(err) | |
| 87 | 87 | } |
| 88 | 88 | if fmtName == "" { |
| 89 | 89 | fmtName = "md" |
| @@ -97,7 +97,7 @@ func runComment(c *Ctx, args []string, t thread, noun string, | ||
| 97 | 97 | } |
| 98 | 98 | body, err := bodyFrom(c, f.Value("--message"), f.Value("--file")) |
| 99 | 99 | if err != nil { |
| 100 | return c.failErr(err) | |
| 100 | return c.failInput(err) | |
| 101 | 101 | } |
| 102 | 102 | if strings.TrimSpace(body) == "" { |
| 103 | 103 | return c.fail(protocol.ExitUsage, "empty comment; use --message or --file -") |
internal/control/token.go +1 −1
| @@ -54,7 +54,7 @@ func runTokenCreate(c *Ctx, args []string) int { | ||
| 54 | 54 | if ttl != "" { |
| 55 | 55 | d, err := parseTTL(ttl) |
| 56 | 56 | if err != nil { |
| 57 | return c.failErr(err) | |
| 57 | return c.failInput(err) | |
| 58 | 58 | } |
| 59 | 59 | t := time.Now().Add(d) |
| 60 | 60 | expires = &t |