╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────╮ │src/serve_http.zig│ ╰──────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │133 var arena = std.heap.ArenaAllocator.init(allocator); │ │134 defer arena.deinit(); │ │135 const moment = try evt.currentMoment(evt.admin_repo_opts, &admin); │ │136 - const repo = (try evt.readRepoByOwnerAndName(io, allocator, &arena, moment, repo_root_path, owner_repo.owner, owner_repo.name)) orelse│ │136 + const repo = (try evt.readRepoByOwnerAndName(io, allocator, &arena, moment, users_dir, owner_repo.owner, owner_repo.name)) orelse │ │137 return error.RepoNotFound; │ │138 if (repo.repo.event.read_access == .private) { │ │139 if (http_server.reader.state == .received_head) http_server.reader.state = .ready; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │140 try writeSimpleResponse(http_server, 403, "Forbidden", "text/plain", "private repos cannot be fetched over HTTP");│ │141 return; │ │142 } │ │143 - const repo_id = std.fmt.bytesToHex(repo.event_id, .lower); │ │144 - break :blk try std.fs.path.join(allocator, &.{ repo_root_path, &repo_id }); │ │143 + break :blk try evt.repoPath(allocator, users_dir, repo.repo.event.user_id, &repo.event_id); │ │144 }; │ │145 defer allocator.free(repo_path); │ │146 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │28 │ │29 pub const SessionHandler = struct { │ │30 admin_repo_path: []const u8, │ │31 - repo_root_path: []const u8, │ │31 + users_dir: []const u8, │ │32 wui_port: u16, // port the web UI is served on, shown in the TUI footer's url│ │33 git_http_port: ?u16, │ │34 git_ssh_port: ?u16, │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │233 ui_session.data.git_http_port = handler.git_http_port; │ │234 ui_session.data.git_ssh_port = handler.git_ssh_port; │ │235 ui_session.data.git_ssh_prefix = handler.git_ssh_prefix; │ │236 - // let page builders open on-disk repos (sibling "repos" dir of the admin repo) │ │236 + // let page builders open on-disk repos │ │237 ui_session.io = io; │ │238 - ui_session.repos_dir = try std.fs.path.join(session_arena.allocator(), &.{ std.fs.path.dirname(handler.admin_repo_path) orelse ".", "repos" });│ │238 + ui_session.users_dir = handler.users_dir; │ │239 │ │240 var nav = try ui.Nav.init(allocator, &ui_session); │ │241 defer nav.deinit(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │380 const owner_repo = evt.parseOwnerRepoPath(repo_identity) orelse return writeError(sess, "repo path must be <owner>/<repo>"); │ │381 var author_arena = std.heap.ArenaAllocator.init(allocator); │ │382 defer author_arena.deinit(); │ │383 - const author = switch (try authorizeRepoKey(io, &author_arena, handler.admin_repo_path, handler.repo_root_path, owner_repo.owner, owner_repo.name, parsed.service, &sess.fingerprint)) {│ │383 + const author = switch (try authorizeRepoKey(io, &author_arena, handler.admin_repo_path, handler.users_dir, owner_repo.owner, owner_repo.name, parsed.service, &sess.fingerprint)) { │ │384 .allowed => |user| user, │ │385 .denied => return switch (parsed.service) { │ │386 .upload_pack => writeError(sess, "unauthorized: this SSH key cannot read this repo"), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │389 .not_found => return writeError(sess, "repo not found"), │ │390 }; │ │391 │ │392 - const repo_path = switch (try serve_common.resolveRepoPath(io, allocator, handler.repo_root_path, handler.admin_repo_path, repo_identity, create_if_missing)) {│ │392 + const repo_path = switch (try serve_common.resolveRepoPath(io, allocator, handler.users_dir, handler.admin_repo_path, repo_identity, create_if_missing)) { │ │393 .ok => |p| p, │ │394 .invalid => unreachable, // parsed above │ │395 .not_found => return writeError(sess, "repo not found"), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │396 }; │ │397 defer allocator.free(repo_path); │ │398 │ │399 - if (try serveIfExists(repo_path, handler.repo_root_path, &reader.interface, &writer.interface, io, allocator, parsed.service, protocol_version, author, handler.err, sess)) return;│ │399 + if (try serveIfExists(repo_path, handler.users_dir, &reader.interface, &writer.interface, io, allocator, parsed.service, protocol_version, author, handler.err, sess)) return; │ │400 │ │401 if (!create_if_missing) return writeError(sess, "repo not found"); │ │402 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │403 // create the on-disk repo for the just-minted event and serve the push │ │404 var repo = try rp.Repo(.xit, any_repo_opts.toRepoOpts()).init(io, allocator, .{ .path = repo_path, .bare = true }); │ │405 defer repo.deinit(io, allocator); │ │406 - try servePack(repo.self_repo_opts, &repo, handler.repo_root_path, &reader.interface, &writer.interface, io, allocator, parsed.service, protocol_version, author, handler.err, sess);│ │406 + try servePack(repo.self_repo_opts, &repo, handler.users_dir, &reader.interface, &writer.interface, io, allocator, parsed.service, protocol_version, author, handler.err, sess); │ │407 } │ │408 │ │409 // serve a patch draft: anyone may fetch it, only its author may push to it │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │431 const fork_id = try evt.parseEventId(&route.id); │ │432 const forker_id = (try evt.User.readIdByName(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, forker_repo.owner)) orelse│ │433 return writeError(sess, "patch draft not found"); │ │434 - const fork_record = (try evt.readForkById(io, allocator, &admin_arena, handler.repo_root_path, &forker_id, &fork_id)) orelse│ │434 + const fork_record = (try evt.readForkById(io, allocator, &admin_arena, handler.users_dir, &forker_id, &fork_id)) orelse │ │435 return writeError(sess, "patch draft not found"); │ │436 if (fork_record.removed) return writeError(sess, "patch draft not found"); │ │437 const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &admin_arena, &forker_id)) orelse │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │440 if (service == .receive_pack and !isKeyInAuthorizedKeys(user.event.ssh_keys, &sess.fingerprint))│ │441 return writeError(sess, "unauthorized: this SSH key is not registered to the patch author");│ │442 │ │443 - const draft_path = try fork.forkPath(allocator, handler.repo_root_path, &route.id); │ │443 + const draft_path = try fork.forkPath(allocator, handler.users_dir, &forker_id, &fork_id); │ │444 defer allocator.free(draft_path); │ │445 var draft = rp.AnyRepo(.xit, any_repo_opts).open(io, allocator, .{ .path = draft_path }) catch │ │446 return writeError(sess, "patch draft not found"); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯