╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │10 const ssh = @import("./serve_ssh_protocol.zig"); │ │11 const evt = @import("./event.zig"); │ │12 const serve_common = @import("./serve_common.zig"); │ │13 +const fork = @import("./fork.zig"); │ │14 │ │15 // listener resource limits. the watchdog gives unauthenticated peers a hard │ │16 // deadline, detects idle non-interactive sessions from protocol progress, and│ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │328 defer parsed.deinit(allocator); │ │329 const repo_prefix = "/repo/"; │ │330 const repo_identity = if (std.mem.startsWith(u8, parsed.dir, repo_prefix)) parsed.dir[repo_prefix.len..] else parsed.dir; │ │331 + │ │332 + if (fork.parseRoute(repo_identity)) |route| { │ │333 + if (evt.parseOwnerRepoPath(route.identity) == null) return writeError(sess, "repo path must be <owner>/<repo>"); │ │334 + if (parsed.service == .receive_pack and !xit.ref.validateName(route.target)) return writeError(sess, "invalid target branch"); │ │335 + │ │336 + const draft_path = try fork.forkPath(allocator, handler.repo_root_path, &route.id); │ │337 + defer allocator.free(draft_path); │ │338 + var draft = rp.AnyRepo(.xit, .{}).open(io, allocator, .{ .path = draft_path }) catch │ │339 + return writeError(sess, "patch draft not found"); │ │340 + defer draft.deinit(io, allocator); │ │341 + switch (draft) { │ │342 + inline else => |*repo| { │ │343 + const author_maybe = try fork.draftAuthor(repo.self_repo_opts, repo, io, allocator); │ │344 + defer if (author_maybe) |author| allocator.free(author); │ │345 + const author = author_maybe orelse return writeError(sess, "invalid patch draft"); │ │346 + if (builtin.mode != .Debug and !try isKeyAuthorized(io, allocator, handler.admin_repo_path, author, &sess.fingerprint))│ │347 + return writeError(sess, "unauthorized: this SSH key is not registered to the patch author"); │ │348 + var reader_buf: [4096]u8 = undefined; │ │349 + var writer_buf: [4096]u8 = undefined; │ │350 + var reader = ssh.SessionReader.init(sess, &reader_buf); │ │351 + var writer = ssh.SessionWriter.init(sess, &writer_buf); │ │352 + switch (parsed.service) { │ │353 + .upload_pack => try repo.uploadPack(io, allocator, &reader.interface, &writer.interface, .{}), │ │354 + .receive_pack => try repo.receivePack(io, allocator, &reader.interface, &writer.interface, .{}), │ │355 + } │ │356 + writer.interface.flush() catch {}; │ │357 + if (parsed.service == .receive_pack) { │ │358 + if (try repo.readRef(io, fork.ref) == null) return writeError(sess, "push HEAD to refs/heads/patch"); │ │359 + try fork.setTarget(repo.self_repo_opts, repo, io, allocator, route.target); │ │360 + } │ │361 + }, │ │362 + } │ │363 + try sess.exit(0); │ │364 + return; │ │365 + } │ │366 │ │367 const create_if_missing = parsed.service == .receive_pack; │ │368 const any_repo_opts: rp.AnyRepoOpts(.xit) = .{}; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯