╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────╮ │150 io: std.Io, │ │151 allocator: std.mem.Allocator,│ │152 repo_root_path: []const u8, │ │153 + admin_repo_path: []const u8, │ │154 + is_test: bool, │ │155 stream: std.Io.net.Stream, │ │156 err: *std.Io.Writer, │ │157 │ ╰──────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │158 fn run(conn: @This()) void { │ │159 defer conn.stream.close(conn.io); │ │148 - handleHttpConnection(repo_kind, any_repo_opts, conn.io, conn.allocator, conn.repo_root_path, conn.stream, conn.err) catch |request_err| { │ │160 + handleHttpConnection(repo_kind, any_repo_opts, conn.io, conn.allocator, conn.repo_root_path, conn.admin_repo_path, conn.is_test, conn.stream, conn.err) catch |request_err| {│ │161 logError(conn.err, "connection failed: {s}\n", .{@errorName(request_err)}); │ │162 }; │ │163 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────╮ │167 .io = ctx.io, │ │168 .allocator = ctx.allocator, │ │169 .repo_root_path = ctx.repo_root_path, │ │170 + .admin_repo_path = ctx.admin_repo_path,│ │171 + .is_test = ctx.is_test, │ │172 .stream = stream, │ │173 .err = ctx.err, │ │174 }}); │ ╰────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────╮ │180 .io = io, │ │181 .allocator = allocator, │ │182 .repo_root_path = repo_root_path, │ │183 + .admin_repo_path = admin_repo_path,│ │184 + .is_test = is_test, │ │185 .net_server = net_server, │ │186 .tasks = tasks, │ │187 .err = err, │ ╰────────────────────────────────────────────────╯ ╭──────────────────────────────────────╮ │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 { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯