Commit 65a1d7220c
Verified · cmc
Layout: unified · split
internal/control/commitfile.go +3
| @@ -72,6 +72,9 @@ func runCommitFile(c *Ctx, args []string) int { | ||
| 72 | 72 | "%s requires signed commits; this writes an unsigned one — push a signed commit instead", |
| 73 | 73 | repo.Path()) |
| 74 | 74 | } |
| 75 | if code := checkStorageQuota(c, repo); code >= 0 { | |
| 76 | return code | |
| 77 | } | |
| 75 | 78 | // A commit carries an identity, and an unverified address is not one. |
| 76 | 79 | email, err := c.Store.PrimaryVerifiedEmail(c.User.ID) |
| 77 | 80 | if err != nil { |
internal/control/quota.go +19
| @@ -81,6 +81,25 @@ func checkRepoQuota(c *Ctx) int { | ||
| 81 | 81 | return -1 |
| 82 | 82 | } |
| 83 | 83 | |
| 84 | // checkStorageQuota refuses a server-side write into repo once its | |
| 85 | // owner's storage quota is used up, the check sshd makes before a push. | |
| 86 | // Repositories an org owns have no quota. | |
| 87 | func checkStorageQuota(c *Ctx, repo store.Repo) int { | |
| 88 | if repo.OwnerKind != "user" { | |
| 89 | return -1 | |
| 90 | } | |
| 91 | limit := ByteLimit(c.Store, limitsOf(c), repo.OwnerID) | |
| 92 | if limit <= 0 { | |
| 93 | return -1 | |
| 94 | } | |
| 95 | if used := OwnedBytes(c.Store, c.Cfg.Server.Root, repo.OwnerID); used >= limit { | |
| 96 | return c.fail(protocol.ExitDenied, | |
| 97 | "%s's storage quota is used up (%d of %d bytes); delete something, or ask an admin to raise the limit", | |
| 98 | repo.OwnerName, used, limit) | |
| 99 | } | |
| 100 | return -1 | |
| 101 | } | |
| 102 | ||
| 84 | 103 | func init() { |
| 85 | 104 | register(Command{Path: []string{"admin", "user", "limits"}, |
| 86 | 105 | Summary: "show or set an account's repository and storage caps (instance admins)", |
internal/control/suggestion.go +3
| @@ -283,6 +283,9 @@ func runMRApplySuggestion(c *Ctx, args []string) int { | ||
| 283 | 283 | "%s requires signed commits and the server cannot sign one; apply it from a clone, which commits with your own key: gitbay mr apply-suggestion %s %d %d", |
| 284 | 284 | source, repo.Path(), mr.Number, threadID) |
| 285 | 285 | } |
| 286 | if code := checkStorageQuota(c, src); code >= 0 { | |
| 287 | return code | |
| 288 | } | |
| 286 | 289 | email, err := c.Store.PrimaryVerifiedEmail(c.User.ID) |
| 287 | 290 | if err != nil { |
| 288 | 291 | return c.failErr(err) |
internal/control/suggestion_test.go +32
| @@ -496,3 +496,35 @@ func TestSuggestionsProcessCountPerFile(t *testing.T) { | ||
| 496 | 496 | t.Fatalf("git processes: %d for one suggestion, %d for six on the same file", one, six) |
| 497 | 497 | } |
| 498 | 498 | } |
| 499 | ||
| 500 | // A server-side write stops at the owner's storage quota as a push does: | |
| 501 | // both apply-suggestion and repo commit-file. | |
| 502 | func TestServerWritesHonourStorageQuota(t *testing.T) { | |
| 503 | f := newSuggestFixture(t, nil) | |
| 504 | f.verified(f.alice) | |
| 505 | id := f.suggest(f.alice, "lib.txt", 1, 1, "ONE") | |
| 506 | full := func(c *Ctx) { | |
| 507 | c.Cfg.Limits.WriteRate = -1 | |
| 508 | c.Cfg.Limits.MaxBytesPerUser = 1 | |
| 509 | } | |
| 510 | before := strings.TrimSpace(f.git(f.dir, "rev-parse", "refs/heads/feature")) | |
| 511 | code, _, errOut := f.runWith(f.alice, full, "mr", "apply-suggestion", f.repo.Path(), "1", id) | |
| 512 | if code != protocol.ExitDenied || !strings.Contains(errOut, "storage quota is used up") { | |
| 513 | t.Errorf("apply-suggestion over quota: exit %d %q", code, errOut) | |
| 514 | } | |
| 515 | code, _, errOut = f.runWith(f.alice, func(c *Ctx) { full(c); c.Stdin = strings.NewReader("x\n") }, | |
| 516 | "repo", "commit-file", f.repo.Path(), "new.txt", "--ref", "feature", "--file", "-") | |
| 517 | if code != protocol.ExitDenied || !strings.Contains(errOut, "storage quota is used up") { | |
| 518 | t.Errorf("commit-file over quota: exit %d %q", code, errOut) | |
| 519 | } | |
| 520 | if after := strings.TrimSpace(f.git(f.dir, "rev-parse", "refs/heads/feature")); after != before { | |
| 521 | t.Fatal("a refused write moved the branch") | |
| 522 | } | |
| 523 | // Under the quota both go through. | |
| 524 | f.mustWrite(f.alice, "mr", "apply-suggestion", f.repo.Path(), "1", id) | |
| 525 | code, _, errOut = f.runWith(f.alice, func(c *Ctx) { unlimited(c); c.Stdin = strings.NewReader("x\n") }, | |
| 526 | "repo", "commit-file", f.repo.Path(), "new.txt", "--ref", "feature", "--file", "-") | |
| 527 | if code != protocol.ExitOK { | |
| 528 | t.Errorf("commit-file under quota: exit %d %q", code, errOut) | |
| 529 | } | |
| 530 | } | |