╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │944 - try evt.consume(.server, .admin, .xit, evt.admin_repo_opts, io, allocator, admin_repo, evt.events_ref, &.{.{│ │944 + try evt.consume(.server, .user, .xit, evt.user_repo_opts, io, allocator, user_repo, evt.events_ref, &.{.{ │ │945 .id = input.id, │ │946 .timestamp = input.timestamp, │ │947 .author = input.author, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────╮ │955 io: std.Io, │ │956 allocator: std.mem.Allocator, │ │957 admin_repo: *rp.Repo(.xit, evt.admin_repo_opts),│ │958 + user_repo: *rp.Repo(.xit, evt.user_repo_opts), │ │959 fork_path: []const u8, │ │960 input: EditDraftInput, │ │961 ) !bool { │ ╰─────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │969 // validate the draft ownership and editing identity │ │970 { │ │971 const admin_moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); │ │971 - const fork_record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &patch_id)) orelse return false; │ │972 + const user_moment = (try evt.userMoment(user_repo)) orelse return false; │ │973 + const fork_record = (try evt.Fork.readById(evt.UserDB, evt.user_repo_opts.hash, user_moment, &arena, &patch_id)) orelse return false; │ │974 if (fork_record.event.stage != .draft) return false; │ │973 - if (fork_record.removed or │ │974 - !std.mem.eql(u8, fork_record.event.user_id, &input.user_id) or │ │975 - !std.mem.eql(u8, fork_record.event.repo_id, &input.repo_id)) return error.InvalidPatchDraft; │ │975 + if (fork_record.removed or !std.mem.eql(u8, fork_record.event.repo_id, &input.repo_id)) return error.InvalidPatchDraft; │ │976 const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &input.user_id)) orelse return error.InvalidPatchDraft;│ │977 if (user.removed or │ │978 !std.mem.eql(u8, user.event.name, input.author.name) or │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────╮ │1168 io: std.Io, │ │1169 allocator: std.mem.Allocator, │ │1170 repo_root_path: []const u8, │ │1171 - admin_repo: *rp.Repo(.xit, evt.admin_repo_opts),│ │1171 target_repo: *rp.Repo(.xit, repo_opts), │ │1172 input: MergeInput, │ │1173 ) !void { │ ╰──────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1175 var arena = std.heap.ArenaAllocator.init(allocator); │ │1176 defer arena.deinit(); │ │1177 │ │1179 - // remove fork data only for patches backed by a fork. │ │1178 + // remove fork data only for patches backed by a fork │ │1179 const record = (try evt.readFromRepo(evt.Patch, .xit, repo_opts, io, allocator, &arena, target_repo, &try evt.parseEventId(&input.id))) orelse return error.InvalidPatch;│ │1181 - if (record.event.source_branch == null) try fork.remove(io, allocator, repo_root_path, admin_repo, &input.id, null, input.author); │ │1180 + if (record.event.source_branch != null) return; │ │1181 + // a fork already cleaned up has no repo left │ │1182 + const fork_path = try fork.forkPath(arena.allocator(), repo_root_path, &input.id); │ │1183 + const forker_id = (try fork.readForkerId(io, allocator, fork_path)) orelse return; │ │1184 + var user_repo = (try evt.openUserRepo(io, allocator, repo_root_path, &forker_id)) orelse return error.InvalidPatchDraft; │ │1185 + defer user_repo.deinit(io, allocator); │ │1186 + try fork.remove(io, allocator, repo_root_path, &user_repo, &input.id, input.author); │ │1187 } │ │1188 │ │1189 fn commitAuthor(line: []const u8) !evt.CommitAuthor { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │151 │ │152 runWebListener(io, allocator, &wui_server, &tasks, .{ .server = .{│ │153 .admin_repo_path = admin_repo_path, │ │154 + .repo_root_path = repo_root_path, │ │155 .session_store = session_store, │ │156 .git_http_port = git_http_port, │ │157 .git_ssh_port = git_ssh_port, │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭ 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.Repo.readByOwnerAndName(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, owner_repo.owner, owner_repo.name)) orelse│ │136 + const repo = (try evt.readRepoByOwnerAndName(io, allocator, &arena, moment, repo_root_path, 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; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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, owner_repo.owner, owner_repo.name, parsed.service, &sess.fingerprint)) { │ │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)) {│ │384 .allowed => |user| user, │ │385 .denied => return switch (parsed.service) { │ │386 .upload_pack => writeError(sess, "unauthorized: this SSH key cannot read this repo"), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │420 const io = sess.conn.io; │ │421 │ │422 const route = fork.parseRoute(fork_path) orelse return writeError(sess, "invalid fork path"); │ │423 - const owner_repo = evt.parseOwnerRepoPath(route.identity) orelse return writeError(sess, "repo path must be <owner>/<repo>"); │ │423 + // the route names the forker and the target repo │ │424 + const forker_repo = evt.parseOwnerRepoPath(route.identity) orelse return writeError(sess, "repo path must be <forker>/<repo>");│ │425 │ │426 var admin = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = handler.admin_repo_path }); │ │427 defer admin.deinit(io, allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │429 defer admin_arena.deinit(); │ │430 const admin_moment = try evt.currentMoment(evt.admin_repo_opts, &admin); │ │431 const fork_id = try evt.parseEventId(&route.id); │ │431 - const fork_record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &admin_arena, &fork_id)) orelse │ │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 │ │435 return writeError(sess, "patch draft not found"); │ │436 if (fork_record.removed) return writeError(sess, "patch draft not found"); │ │434 - const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &admin_arena, fork_record.event.user_id)) orelse│ │437 + const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &admin_arena, &forker_id)) orelse │ │438 return writeError(sess, "invalid patch draft"); │ │439 if (user.removed) return writeError(sess, "invalid patch draft"); │ │440 if (service == .receive_pack and !isKeyInAuthorizedKeys(user.event.ssh_keys, &sess.fingerprint)) │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯