╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭──────────────────────────────────────╮ │194 io: std.Io, │ │195 allocator: std.mem.Allocator,│ │196 repo_root_path: []const u8, │ │197 + admin_repo_path: []const u8, │ │198 + is_test: bool, │ │199 stream: std.Io.net.Stream, │ │200 err: *std.Io.Writer, │ │201 ) !void { │ ╰──────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │212 else => |e| return e, │ │213 }; │ │214 │ │197 - handleHttpGitRequest(repo_kind, any_repo_opts, io, allocator, repo_root_path, &http_server, &request) catch |request_err| { │ │215 + handleHttpGitRequest(repo_kind, any_repo_opts, io, allocator, repo_root_path, admin_repo_path, is_test, &http_server, &request) catch |request_err| {│ │216 try err.print("request failed: {s}\n", .{@errorName(request_err)}); │ │217 try err.flush(); │ │218 if (http_server.reader.state == .received_head) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────╮ │231 io: std.Io, │ │232 allocator: std.mem.Allocator, │ │233 repo_root_path: []const u8, │ │234 + admin_repo_path: []const u8, │ │235 + is_test: bool, │ │236 http_server: *std.http.Server, │ │237 request: *std.http.Server.Request,│ │238 ) !void { │ ╰───────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │255 const repo_rel = try decodeAndValidateRepoPath(allocator, repo_rel_encoded); │ │256 defer allocator.free(repo_rel); │ │257 │ │238 - const repo_path = try std.fs.path.resolve(allocator, &.{ repo_root_path, repo_rel }); │ │239 - defer allocator.free(repo_path); │ │240 - │ │241 - if (!isSubPath(repo_root_path, repo_path)) { │ │242 - if (http_server.reader.state == .received_head) { │ │243 - http_server.reader.state = .ready; │ │244 - } │ │245 - try writeSimpleResponse(http_server, 403, "Forbidden", "text/plain", "forbidden"); │ │246 - return; │ │247 - } │ │248 - │ │258 const request_method = normalizeMethod(request.head.method); │ │259 const content_type = try allocator.dupe(u8, findHeader(request, "content-type") orelse "");│ │260 defer allocator.free(content_type); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │270 if (http_server.reader.state == .received_head) { │ │271 http_server.reader.state = .ready; │ │272 } │ │273 + │ │274 + const create_if_missing = isReceivePack(handler, suffix, uri.query); │ │275 │ │276 + // derive the on-disk repo path. normally resolve <owner>/<repo> through the │ │277 + // event store and serve its event-id dir; in test mode serve the path directly │ │278 + const repo_path = if (is_test) blk: { │ │279 + const direct_path = try std.fs.path.resolve(allocator, &.{ repo_root_path, repo_rel }); │ │280 + if (!isSubPath(repo_root_path, direct_path)) { │ │281 + allocator.free(direct_path); │ │282 + try writeSimpleResponse(http_server, 403, "Forbidden", "text/plain", "forbidden"); │ │283 + return; │ │284 + } │ │285 + break :blk direct_path; │ │286 + } else blk: { │ │287 + const owner_repo = evt.parseOwnerRepoPath(repo_rel) orelse { │ │288 + try writeSimpleResponse(http_server, 400, "Bad Request", "text/plain", "repo path must be <owner>/<repo>"); │ │289 + return; │ │290 + }; │ │291 + const event_id_hex = (try evt.resolveOrCreateRepo(io, allocator, admin_repo_path, owner_repo.owner, owner_repo.name, create_if_missing)) orelse return error.RepoNotFound;│ │292 + break :blk try std.fs.path.join(allocator, &.{ repo_root_path, &event_id_hex }); │ │293 + }; │ │294 + defer allocator.free(repo_path); │ │295 + │ │296 var body_reader = std.Io.Reader.fixed(body); │ │266 - const create_if_missing = isReceivePack(handler, suffix, uri.query); │ │297 const http_backend_options = xit.net_server_http_backend.Options{ │ │298 .request_method = request_method, │ │299 .handler = handler, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │405 create_if_missing: bool, │ │406 service: anytype, │ │407 ) !void { │ │408 + // serve the existing on-disk repo at its event-id directory │ │409 + if (try serveIfExists(repo_kind, any_repo_opts, io, allocator, repo_path, service)) return; │ │410 + if (!create_if_missing) return error.RepoNotFound; │ │411 + │ │412 + // create the on-disk repo for the just-minted event and serve the push │ │413 if (any_repo_opts.hash) |hash_kind| { │ │379 - var repo = try openRepo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), io, allocator, repo_path, create_if_missing);│ │414 + var repo = try openRepo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), io, allocator, repo_path, true); │ │415 defer repo.deinit(io, allocator); │ │416 try service.serve(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), &repo, io, allocator); │ │417 } else { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │383 - var any_repo = rp.AnyRepo(repo_kind, any_repo_opts).open(io, allocator, .{ .path = repo_path }) catch |open_err| switch (open_err) { │ │384 - error.RepoNotFound => { │ │385 - if (!create_if_missing) return open_err; │ │418 + var repo = try openRepo(repo_kind, any_repo_opts.toRepoOpts(), io, allocator, repo_path, true); │ │419 + defer repo.deinit(io, allocator); │ │420 + try service.serve(repo_kind, any_repo_opts.toRepoOpts(), &repo, io, allocator); │ │421 + } │ │422 +} │ │423 │ │387 - var repo = try openRepo(repo_kind, any_repo_opts.toRepoOpts(), io, allocator, repo_path, true); │ │388 - defer repo.deinit(io, allocator); │ │389 - try service.serve(repo_kind, any_repo_opts.toRepoOpts(), &repo, io, allocator); │ │390 - return; │ │391 - }, │ │424 +// serve an existing repo at `repo_path`, or return false if none is there │ │425 +fn serveIfExists( │ │426 + comptime repo_kind: rp.RepoKind, │ │427 + comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), │ │428 + io: std.Io, │ │429 + allocator: std.mem.Allocator, │ │430 + repo_path: []const u8, │ │431 + service: anytype, │ │432 +) !bool { │ │433 + // a bare open() creates the directory while probing for the repo, so only │ │434 + // attempt it when the path already exists │ │435 + std.Io.Dir.accessAbsolute(io, repo_path, .{}) catch return false; │ │436 + │ │437 + if (any_repo_opts.hash) |hash_kind| { │ │438 + var repo = rp.Repo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind)).open(io, allocator, .{ .path = repo_path }) catch |open_err| switch (open_err) {│ │439 + error.RepoNotFound => return false, │ │440 else => |e| return e, │ │441 }; │ │442 + defer repo.deinit(io, allocator); │ │443 + try service.serve(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), &repo, io, allocator); │ │444 + } else { │ │445 + var any_repo = rp.AnyRepo(repo_kind, any_repo_opts).open(io, allocator, .{ .path = repo_path }) catch |open_err| switch (open_err) { │ │446 + error.RepoNotFound => return false, │ │447 + else => |e| return e, │ │448 + }; │ │449 defer any_repo.deinit(io, allocator); │ │395 - │ │450 switch (any_repo) { │ │397 - inline else => |*repo| { │ │398 - try service.serve(repo.self_repo_kind, repo.self_repo_opts, repo, io, allocator); │ │399 - }, │ │451 + inline else => |*repo| try service.serve(repo.self_repo_kind, repo.self_repo_opts, repo, io, allocator), │ │452 } │ │453 } │ │454 + return true; │ │455 } │ │456 │ │457 fn openRepo( │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯