╭ changed ──────╮ │src/command.zig│ ╰───────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │22 \\haxy serve --web-listen 127.0.0.1:8080 --ssh-listen 127.0.0.1:8022 --data-dir /srv/git │ │23 \\use "none" for --ssh-listen to disable ssh. git clones over HTTP share the web UI's listener. │ │24 \\--web-url and --ssh-url set the public urls shown to users, like https://example.com and ssh://example.com.│ │25 + \\--max-repo-size caps each repo's on-disk size, like 1G, rejecting pushes past it. no limit by default. │ │26 , │ │27 }, │ │28 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────╮ │89 .{"--ssh-listen"}, │ │90 .{"--web-url"}, │ │91 .{"--ssh-url"}, │ │92 + .{"--max-repo-size"},│ │93 .{"--data-dir"}, │ │94 }); │ │95 │ ╰─────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │213 // urls are joined with "/" + a path │ │214 if (cmd_args.get("--web-url")) |value_maybe| options.web_url = std.mem.trimEnd(u8, value_maybe orelse return error.WebUrlNeedsValue, "/"); │ │215 if (cmd_args.get("--ssh-url")) |value_maybe| options.ssh_url = std.mem.trimEnd(u8, value_maybe orelse return error.SshUrlNeedsValue, "/"); │ │216 + if (cmd_args.get("--max-repo-size")) |value_maybe| options.max_repo_size = try parseSize(value_maybe orelse return error.MaxRepoSizeNeedsValue);│ │217 if (cmd_args.get("--data-dir")) |value_maybe| options.data_dir = value_maybe orelse return error.DataDirNeedsValue; │ │218 return options; │ │219 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │220 │ │221 +// a byte count with an optional K, M or G suffix │ │222 +fn parseSize(value: []const u8) !u64 { │ │223 + if (value.len == 0) return error.InvalidSize; │ │224 + const shift: u6 = switch (value[value.len - 1]) { │ │225 + 'K', 'k' => 10, │ │226 + 'M', 'm' => 20, │ │227 + 'G', 'g' => 30, │ │228 + else => 0, │ │229 + }; │ │230 + const digits = if (shift == 0) value else value[0 .. value.len - 1]; │ │231 + const count = std.fmt.parseInt(u64, digits, 10) catch return error.InvalidSize; │ │232 + return std.math.shlExact(u64, count, shift) catch error.InvalidSize; │ │233 +} │ │234 + │ │235 /// parses the given args into a command if valid, and determines how it should be run│ │236 /// (via the TUI or CLI). │ │237 pub const CommandDispatch = union(enum) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────╮ │src/progress.zig│ ╰────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │20 sess: ?*ssh.SessionCtx = null, │ │21 // set once it has hung up, so the phase in flight can stop too │ │22 gone: bool = false, │ │23 + // a pushed repo's db and the size it may grow to │ │24 + size_limit: ?SizeLimit = null, │ │25 + // set once the db outgrew size_limit, so the phase in flight can stop too│ │26 + too_large: bool = false, │ │27 label: []const u8 = "Processing commits", │ │28 count: usize = 0, │ │29 total: usize = 0, │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │32 │ │33 // how many items a phase with no total advances between reports │ │34 const report_every = 1000; │ │35 + │ │36 + pub const SizeLimit = struct { │ │37 + file: std.Io.File, │ │38 + max: u64, │ │39 + }; │ │40 + │ │41 + // fail once the pushed repo's db has outgrown its limit │ │42 + pub fn checkSize(self: *Sideband, io: std.Io) !void { │ │43 + const limit = self.size_limit orelse return; │ │44 + if (try limit.file.length(io) <= limit.max) return; │ │45 + self.too_large = true; │ │46 + return error.RepoTooLarge; │ │47 + } │ │48 │ │49 // the phases a client is told about, listed so a new kind has to be │ │50 // decided on rather than inherit whatever label was last set. unpacking is│ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │57 }; │ │58 } │ │59 │ │43 - pub fn run(self: *Sideband, _: std.Io, event: rp.ProgressEvent) !void { │ │60 + pub fn run(self: *Sideband, io: std.Io, event: rp.ProgressEvent) !void { │ │61 + // every unpacked object reports, so a push stops soon after crossing the limit│ │62 + if (event == .complete_one) try self.checkSize(io); │ │63 switch (event) { │ │64 .start => |start| { │ │65 if (!reported(start.kind)) return; │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │124 } │ │125 }; │ │126 │ │108 -// whether the client the work is reporting to has hung up. only a sideband │ │109 -// reporter probes, so any other context never cancels. │ │127 +// whether the client the work is reporting to has hung up, or its push has │ │128 +// outgrown the repo size limit. only a sideband reporter probes, so any other │ │129 +// context never cancels. │ │130 pub fn cancelled(comptime repo_opts: rp.RepoOpts(.xit), progress_ctx_maybe: ?repo_opts.ProgressCtx) bool {│ │131 if (repo_opts.ProgressCtx != *Sideband) return false; │ │132 const progress_ctx = progress_ctx_maybe orelse return false; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────╮ │113 - return progress_ctx.gone; │ │133 + return progress_ctx.gone or progress_ctx.too_large;│ │134 } │ │135 │ ╰────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/push.zig│ ╰────────────╯ ╭─────────────────────────────────────────────────╮ │123 author: evt.CommitAuthor, │ │124 pusher_id: *const [evt.event_id_size]u8,│ │125 server: evt.Host.Server, │ │126 + max_repo_size: ?u64, │ │127 error_writer: *std.Io.Writer, │ │128 sess: ?*ssh.SessionCtx, │ │129 ) !void { │ ╰─────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯