╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │162 } │ │163 │ │164 var body_reader = std.Io.Reader.fixed(body); │ │165 + const create_if_missing = isReceivePack(handler, suffix, uri.query); │ │166 + const http_backend_options = xit.net_server_http_backend.Options{ │ │167 + .request_method = request_method, │ │168 + .handler = handler, │ │169 + .suffix = suffix, │ │170 + .query_string = if (uri.query) |q| q.percent_encoded else "", │ │171 + .content_type = content_type, │ │172 + .has_remote_user = has_remote_user, │ │173 + .protocol_version = protocol_version, │ │174 + }; │ │175 + │ │176 if (any_repo_opts.hash) |hash_kind| { │ │166 - var repo = try rp.Repo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind)).open(io, allocator, .{ .path = repo_path }); │ │177 + var repo = try openRepo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), io, allocator, repo_path, create_if_missing); │ │178 defer repo.deinit(io, allocator); │ │168 - try runHttpBackend(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), &repo, io, allocator, &body_reader, http_server.out, request_method, handler, suffix, uri.query, content_type, has_remote_user, protocol_version);│ │179 + try repo.httpBackend(io, allocator, &body_reader, http_server.out, .http, http_backend_options); │ │180 } else { │ │170 - var any_repo = try rp.AnyRepo(repo_kind, any_repo_opts).open(io, allocator, .{ .path = repo_path }); │ │181 + var any_repo = rp.AnyRepo(repo_kind, any_repo_opts).open(io, allocator, .{ .path = repo_path }) catch |err| switch (err) { │ │182 + error.RepoNotFound => { │ │183 + if (!create_if_missing) return err; │ │184 + │ │185 + var repo = try openRepo(repo_kind, any_repo_opts.toRepoOpts(), io, allocator, repo_path, true); │ │186 + defer repo.deinit(io, allocator); │ │187 + │ │188 + try repo.httpBackend(io, allocator, &body_reader, http_server.out, .http, http_backend_options); │ │189 + return; │ │190 + }, │ │191 + else => |e| return e, │ │192 + }; │ │193 defer any_repo.deinit(io, allocator); │ │194 + │ │195 switch (any_repo) { │ │196 inline else => |*repo| { │ │174 - try runHttpBackend(repo.self_repo_kind, repo.self_repo_opts, repo, io, allocator, &body_reader, http_server.out, request_method, handler, suffix, uri.query, content_type, has_remote_user, protocol_version); │ │197 + try repo.httpBackend(io, allocator, &body_reader, http_server.out, .http, http_backend_options); │ │198 }, │ │199 } │ │200 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │178 -} │ │179 - │ │180 -fn runHttpBackend( │ │181 - comptime repo_kind: rp.RepoKind, │ │182 - comptime repo_opts: rp.RepoOpts(repo_kind), │ │183 - repo: *rp.Repo(repo_kind, repo_opts), │ │184 - io: std.Io, │ │185 - allocator: std.mem.Allocator, │ │186 - reader: *std.Io.Reader, │ │187 - writer: *std.Io.Writer, │ │188 - request_method: std.http.Method, │ │189 - handler: xit.net_server_http_backend.HandlerKind, │ │190 - suffix: []const u8, │ │191 - query: ?std.Uri.Component, │ │192 - content_type: []const u8, │ │193 - has_remote_user: bool, │ │194 - protocol_version: xit.net_server_common.ProtocolVersion, │ │195 -) !void { │ │196 - try repo.httpBackend(io, allocator, reader, writer, .http, .{ │ │197 - .request_method = request_method, │ │198 - .handler = handler, │ │199 - .suffix = suffix, │ │200 - .query_string = if (query) |q| q.percent_encoded else "", │ │201 - .content_type = content_type, │ │202 - .has_remote_user = has_remote_user, │ │203 - .protocol_version = protocol_version, │ │204 - }); │ │201 } │ │202 │ │203 fn findRoute(path: []const u8) ?struct { xit.net_server_http_backend.HandlerKind, []const u8 } {│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │277 if (!isSubPath(project_root, repo_path)) return error.Forbidden; │ │278 │ │279 if (any_repo_opts.hash) |hash_kind| { │ │284 - var repo = try rp.Repo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind)).open(io, allocator, .{ .path = repo_path }); │ │280 + var repo = try openRepo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), io, allocator, repo_path, request.service == .receive_pack); │ │281 defer repo.deinit(io, allocator); │ │282 try runSshService(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), &repo, io, allocator, &reader.interface, &writer.interface, request);│ │283 } else { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │288 - var any_repo = try rp.AnyRepo(repo_kind, any_repo_opts).open(io, allocator, .{ .path = repo_path }); │ │284 + var any_repo = rp.AnyRepo(repo_kind, any_repo_opts).open(io, allocator, .{ .path = repo_path }) catch |open_err| switch (open_err) { │ │285 + error.RepoNotFound => { │ │286 + if (request.service != .receive_pack) return open_err; │ │287 + │ │288 + var repo = try openRepo(repo_kind, any_repo_opts.toRepoOpts(), io, allocator, repo_path, true); │ │289 + defer repo.deinit(io, allocator); │ │290 + try runSshService(repo_kind, any_repo_opts.toRepoOpts(), &repo, io, allocator, &reader.interface, &writer.interface, request);│ │291 + return; │ │292 + }, │ │293 + else => |e| return e, │ │294 + }; │ │295 defer any_repo.deinit(io, allocator); │ │296 switch (any_repo) { │ │297 inline else => |*repo| { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │319 .protocol_version = request.protocol_version, │ │320 }), │ │321 } │ │322 +} │ │323 + │ │324 +fn openRepo( │ │325 + comptime repo_kind: rp.RepoKind, │ │326 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │327 + io: std.Io, │ │328 + allocator: std.mem.Allocator, │ │329 + repo_path: []const u8, │ │330 + create_if_missing: bool, │ │331 +) !rp.Repo(repo_kind, repo_opts) { │ │332 + return rp.Repo(repo_kind, repo_opts).open(io, allocator, .{ .path = repo_path }) catch |open_err| switch (open_err) {│ │333 + error.RepoNotFound => { │ │334 + if (!create_if_missing) return open_err; │ │335 + │ │336 + var repo = try rp.Repo(repo_kind, repo_opts).init(io, allocator, .{ .path = repo_path }); │ │337 + errdefer repo.deinit(io, allocator); │ │338 + try repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" }); │ │339 + try repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" }); │ │340 + return repo; │ │341 + }, │ │342 + else => |e| return e, │ │343 + }; │ │344 } │ │345 │ │346 +fn isReceivePack( │ │347 + handler: xit.net_server_http_backend.HandlerKind, │ │348 + suffix: []const u8, │ │349 + query: ?std.Uri.Component, │ │350 +) bool { │ │351 + return switch (handler) { │ │352 + .run_service => std.mem.eql(u8, suffix, "/git-receive-pack"), │ │353 + .get_info_refs => if (query) |q| │ │354 + std.mem.startsWith(u8, q.percent_encoded, "service=git-receive-pack") │ │355 + else │ │356 + false, │ │357 + }; │ │358 +} │ │359 + │ │360 fn readSshRequest(allocator: std.mem.Allocator, reader: *std.Io.Reader) !SshRequest { │ │361 const magic = try readSshPreludeLine(reader); │ │362 if (!std.mem.eql(u8, magic, "haxy-ssh-helper-v1")) return error.InvalidSshMagic; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯