╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ serve_common.zig ─────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
6 pub const RepoPath = union(enum) { 7 ok: []const u8, // resolved on-disk path; the caller owns and frees it 8 invalid, // a <owner>/<repo> path was expected but not given 9 not_found, // unknown owner, or the repo doesn't exist and we aren't creating 10 }; 11 12 // resolve a requested repo path to its on-disk directory, parsed as 13 // <owner>/<repo> through the event store, minting a repo event for a fresh push. 14 pub fn resolveRepoPath( 15 io: std.Io, 16 allocator: std.mem.Allocator, 17 repo_root_path: []const u8, 18 admin_repo_path: []const u8, 19 requested: []const u8, 20 create_if_missing: bool, 21 ) !RepoPath { 22 const owner_repo = evt.parseOwnerRepoPath(requested) orelse return .invalid; 23 const event_id_hex = (try evt.resolveOrCreateRepo( 24 io, 25 allocator, 26 admin_repo_path, 27 owner_repo.owner, 28 owner_repo.name, 29 if (create_if_missing) .{} else null, 30 )) orelse return .not_found; 31 return .{ .ok = try std.fs.path.join(allocator, &.{ repo_root_path, &event_id_hex }) }; 32 } 33 34 // every connection task shares one error writer, so writing to it takes a 35 // lock. uncancelable, since a log line shouldn't be a cancelation point. 36 var log_mutex: std.Io.Mutex = .init; 37 38 pub fn logError(io: std.Io, err: *std.Io.Writer, comptime fmt: []const u8, args: anytype) void { 39 log_mutex.lockUncancelable(io); 40 defer log_mutex.unlock(io); 41 err.print(fmt, args) catch return; 42 err.flush() catch {}; 43 } 44 45 // accept connections forever, spawning `handleConn(context, stream)` as a task 46 // for each. the handler owns the stream (it closes it). `name` labels accept 47 // errors in the log. shared by the http, ssh, and web ui listeners. 48 pub fn runListener( 49 io: std.Io, 50 net_server: *std.Io.net.Server, 51 tasks: *std.Io.Group, 52 err: *std.Io.Writer, 53 name: []const u8, 54 context: anytype, 55 comptime handleConn: fn (@TypeOf(context), std.Io.net.Stream) void, 56 ) void { 57 const Context = @TypeOf(context); 58 59 const Conn = struct { 60 context: Context, 61 stream: std.Io.net.Stream, 62 63 fn run(c: @This()) void { 64 handleConn(c.context, c.stream); 65 } 66 }; 67 68 const Listener = struct { 69 io: std.Io, 70 net_server: *std.Io.net.Server, 71 tasks: *std.Io.Group, 72 err: *std.Io.Writer, 73 name: []const u8, 74 context: Context, 75 76 fn run(self: @This()) void { 77 while (true) { 78 const stream = self.net_server.accept(self.io) catch |accept_err| { 79 if (accept_err == error.Canceled) return; 80 logError(self.io, self.err, "{s} accept failed: {s}\n", .{ self.name, @errorName(accept_err) }); 81 continue; 82 }; 83 self.tasks.async(self.io, Conn.run, .{Conn{ .context = self.context, .stream = stream }}); 84 } 85 } 86 }; 87 88 tasks.async(io, Listener.run, .{Listener{ 89 .io = io, 90 .net_server = net_server, 91 .tasks = tasks, 92 .err = err, 93 .name = name, 94 .context = context, 95 }}); 96 } 97