A free workspace's public repositories hold up to 5 GB together. Public repositories are never charged for storage and the only cap was 950 MB a repository, with no limit on how many, so a free workspace could keep any amount at g1t's cost (and in the nightly backups). A push to a public repository now adds up what the workspace's public repositories hold, as g1t counts pushes, and past FREE_PUBLIC_STORAGE_BYTES (5 GB, a wrangler var; 0 is no cap) asks billing whether the workspace is free; if it is, the push is refused on its first request with what it holds and what to do, as pushes to a free workspace's full private repositories already are. On the plan nothing changes, and nothing already stored is touched. The git and usage guides say so.
6 files+78−40/6 viewed
| 326 | 326 | | --- | --- | --- | | |
| 327 | 327 | | A file | 32 MB | The push is declined, naming the file's size. | | |
| 328 | 328 | | A repository, with its pull requests' forks | 950 MB, as g1t counts what was pushed (the store holds 1 GB) | The push is declined; once full, pushes are refused with the reason before any data is sent. | | |
| 329 | + | | A free workspace's repositories | 1 GB private and 5 GB public, together; none on the g1t plan | Pushes to the full kind are refused with the reason before any data is sent; see [usage and billing](/guides/usage-and-billing/). | | |
| 329 | 330 | | A push that push protection can scan before it lands | Most pushes; very large ones are scanned after they land | A very large push goes through and is scanned after it lands; secrets found are open alerts. To have it checked first, push in parts, oldest commits first. | | |
| 330 | 331 | | A push | 100 MB | Refused by the network with HTTP `413` before g1t sees it. | | |
| 331 | 332 |
| 32 | 32 | - The [audit log](/guides/audit-log/), with export: kept 7 days, or 90 on | |
| 33 | 33 | the plan. | |
| 34 | 34 | - Your own agent through [MCP](/reference/mcp/). | |
| 35 | − | - 1 GB of private repository storage, and 50,000 | |
| 36 | − | [git operations](#git-operations) a month. | |
| 35 | + | - 1 GB of private repository storage, 5 GB of public repository storage, | |
| 36 | + | and 50,000 [git operations](#git-operations) a month. | |
| 37 | 37 | ||
| 38 | 38 | None of this is charged, on the plan or not. A free workspace is never | |
| 39 | 39 | charged for storage: once its private repositories hold 1 GB, pushes to | |
| 40 | 40 | them are refused at the start of the push, with what to do: make the | |
| 41 | 41 | repository public, delete what you do not need, or start the plan, where | |
| 42 | 42 | storage past 1 GB is usage and pushes never stop. Public repositories are | |
| 43 | − | never charged for storage. | |
| 43 | + | never charged for storage, on any plan; a free workspace's public | |
| 44 | + | repositories hold up to 5 GB together, and past it pushes to them are | |
| 45 | + | refused the same way. On the plan, pushes to public repositories never | |
| 46 | + | stop. | |
| 44 | 47 | ||
| 45 | 48 | Compute is not free. Agents, workflows, the merge queue, | |
| 46 | 49 | deployments and semantic search need [the g1t plan](#the-g1t-plan), or a |
| 304 | 304 | .unwrap_or(1_000_000_000) | |
| 305 | 305 | } | |
| 306 | 306 | ||
| 307 | + | /// What a free workspace's public repositories may hold together: | |
| 308 | + | /// `FREE_PUBLIC_STORAGE_BYTES` (5 GB). Public repositories are never | |
| 309 | + | /// charged for storage, on any plan; past it, a free workspace's pushes to | |
| 310 | + | /// them stop, and on the plan they never do. `0` is no cap. | |
| 311 | + | pub fn free_public_bytes(env: &worker::Env) -> i64 { | |
| 312 | + | env.var("FREE_PUBLIC_STORAGE_BYTES") | |
| 313 | + | .ok() | |
| 314 | + | .and_then(|value| value.to_string().parse().ok()) | |
| 315 | + | .unwrap_or(5_000_000_000) | |
| 316 | + | } | |
| 317 | + | ||
| 318 | + | /// Whether a free workspace's push to a public repository should be | |
| 319 | + | /// refused: its public repositories already hold `free_bytes`. | |
| 320 | + | pub fn public_storage_full(public_bytes: i64, free_bytes: i64) -> bool { | |
| 321 | + | free_bytes > 0 && public_bytes >= free_bytes | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | /// The answer to a push to a public repository a free workspace has no | |
| 325 | + | /// room for. | |
| 326 | + | pub fn public_storage_full_response(namespace: &str, public_bytes: i64, free_bytes: i64) -> Result<Response> { | |
| 327 | + | let gb = |bytes: i64| bytes as f64 / 1_000_000_000.0; | |
| 328 | + | let message = format!( | |
| 329 | + | "{namespace}'s public repositories hold {:.2} GB, and a workspace on the free plan keeps {:.0} GB of them. Public repositories are never charged for storage, so pushes to them stop here. Delete what you no longer need, or start the g1t plan, where pushes to public repositories never stop: https://g1t.sh/{namespace}/-/billing | |
| 330 | + | ", | |
| 331 | + | gb(public_bytes), | |
| 332 | + | gb(free_bytes) | |
| 333 | + | ); | |
| 334 | + | Response::error(message, 403) | |
| 335 | + | } | |
| 336 | + | ||
| 307 | 337 | /// Whether a push to a private repository should be refused: a free | |
| 308 | 338 | /// workspace whose private repositories already hold its free amount. Free | |
| 309 | 339 | /// workspaces are never charged for storage; past it, pushes stop instead. | |
| ⋯ | |||
| 419 | 449 | } | |
| 420 | 450 | ||
| 421 | 451 | #[test] | |
| 452 | + | fn a_free_workspace_pushes_until_its_public_storage_is_full() { | |
| 453 | + | assert!(!public_storage_full(4_999_999_999, 5_000_000_000)); | |
| 454 | + | assert!(public_storage_full(5_000_000_000, 5_000_000_000)); | |
| 455 | + | assert!(!public_storage_full(50_000_000_000, 0)); | |
| 456 | + | } | |
| 457 | + | ||
| 458 | + | #[test] | |
| 422 | 459 | fn a_free_workspace_pushes_until_its_private_storage_is_full() { | |
| 423 | 460 | assert!(!storage_full(999_999_999, 1_000_000_000)); | |
| 424 | 461 | assert!(storage_full(1_000_000_000, 1_000_000_000)); | |
| 2216 | 2216 | ))); | |
| 2217 | 2217 | } | |
| 2218 | 2218 | } | |
| 2219 | + | if git.service == GitService::ReceivePack && git.endpoint == "info/refs" && !repo.is_private { | |
| 2220 | + | let free = git_ops::free_public_bytes(env); | |
| 2221 | + | if free > 0 { | |
| 2222 | + | let held = self.registry.public_bytes(&namespace).await.unwrap_or(0); | |
| 2223 | + | if git_ops::public_storage_full(held, free) | |
| 2224 | + | && git_ops::is_free(env.service("BILLING").ok().as_ref(), &namespace).await | |
| 2225 | + | { | |
| 2226 | + | return Ok(Some(( | |
| 2227 | + | git_ops::public_storage_full_response(&namespace, held, free)?, | |
| 2228 | + | 403, | |
| 2229 | + | "Free public storage is full.", | |
| 2230 | + | ))); | |
| 2231 | + | } | |
| 2232 | + | } | |
| 2233 | + | } | |
| 2219 | 2234 | Ok(None) | |
| 2220 | 2235 | } | |
| 2221 | 2236 |
| 636 | 636 | } | |
| 637 | 637 | ||
| 638 | 638 | /// What one workspace's private repositories are counted as holding. | |
| 639 | + | pub async fn public_bytes(&self, namespace: &str) -> Result<i64> { | |
| 640 | + | #[derive(Deserialize)] | |
| 641 | + | struct Row { | |
| 642 | + | bytes: Option<f64>, | |
| 643 | + | } | |
| 644 | + | Ok(self | |
| 645 | + | .db | |
| 646 | + | .prepare("SELECT SUM(stored_bytes) AS bytes FROM repos WHERE namespace = ? AND is_private = 0 AND fork_of IS NULL AND deleted_at IS NULL") | |
| 647 | + | .bind(&[namespace.into()])? | |
| 648 | + | .first::<Row>(None) | |
| 649 | + | .await? | |
| 650 | + | .and_then(|row| row.bytes) | |
| 651 | + | .unwrap_or(0.0) as i64) | |
| 652 | + | } | |
| 653 | + | ||
| 639 | 654 | pub async fn private_bytes(&self, namespace: &str) -> Result<i64> { | |
| 640 | 655 | #[derive(Deserialize)] | |
| 641 | 656 | struct Row { |
| 65 | 65 | // a workspace on the plan pays at cost plus 20% and is never slowed. | |
| 66 | 66 | // FREE_PRIVATE_STORAGE_BYTES: a free workspace's private repositories may | |
| 67 | 67 | // hold this much (1 GB); past it, pushes to them stop. Billing reads the | |
| 68 | − | // same amount. | |
| 68 | + | // same amount. FREE_PUBLIC_STORAGE_BYTES: a free workspace's public | |
| 69 | + | // repositories may hold this much together (5 GB); past it, pushes to | |
| 70 | + | // them stop. 0 is no cap. | |
| 69 | 71 | // | |
| 70 | 72 | // The git store: | |
| 71 | 73 | // REPO_STORAGE_LIMIT_BYTES: pushes stop before a repository holds this | |
| ⋯ | |||
| 106 | 108 | "GIT_OPERATIONS_FREE_CAP": "50000", | |
| 107 | 109 | "GIT_OPERATIONS_FREE_HOURLY": "60", | |
| 108 | 110 | "FREE_PRIVATE_STORAGE_BYTES": "1000000000", | |
| 111 | + | "FREE_PUBLIC_STORAGE_BYTES": "5000000000", | |
| 109 | 112 | "REPO_STORAGE_LIMIT_BYTES": "950000000", | |
| 110 | 113 | "LARGE_PUSHES": "unscanned", | |
| 111 | 114 | "FORK_RETENTION_DAYS": "1", | |
Comments
Sign in to comment.