╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭─────────────────────────────────────────────────────────╮ │436 repo_root_path: []const u8, │ │437 admin_repo: *rp.Repo(.xit, evt.admin_repo_opts),│ │438 id: *const [evt.event_id_size * 2]u8, │ │439 - user_id: *const [evt.event_id_size]u8, │ │439 + expected_user_id: ?*const [evt.event_id_size]u8,│ │440 author: evt.CommitAuthor, │ │441 ) !void { │ │442 const fork_id = try evt.parseEventId(id); │ ╰─────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │444 defer arena.deinit(); │ │445 const moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); │ │446 const record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.InvalidPatchDraft;│ │447 - if (!std.mem.eql(u8, record.event.user_id, user_id)) return error.InvalidPatchDraft; │ │447 + if (expected_user_id) |user_id| { │ │448 + if (!std.mem.eql(u8, record.event.user_id, user_id)) return error.InvalidPatchDraft; │ │449 + } │ │450 if (!record.removed) try evt.remove(.admin, .xit, evt.admin_repo_opts, io, allocator, admin_repo, &fork_id, .fork, author); │ │451 │ │452 const path = try forkPath(allocator, repo_root_path, id); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │291 .timestamp = input.timestamp, │ │292 }); │ │293 } │ │294 +} │ │295 + │ │296 +// merge a published patch and remove its fork │ │297 +pub fn mergeAndRemoveFork( │ │298 + comptime repo_opts: rp.RepoOpts(.xit), │ │299 + io: std.Io, │ │300 + allocator: std.mem.Allocator, │ │301 + repo_root_path: []const u8, │ │302 + admin_repo: *rp.Repo(.xit, evt.admin_repo_opts), │ │303 + target_repo: *rp.Repo(.xit, repo_opts), │ │304 + input: MergeInput, │ │305 +) !void { │ │306 + try merge(repo_opts, io, allocator, repo_root_path, target_repo, input); │ │307 + try fork.remove(io, allocator, repo_root_path, admin_repo, &input.id, null, input.author);│ │308 } │ │309 │ │310 fn commitAuthor(line: []const u8) !evt.CommitAuthor { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │763 // merge the selected revision into the target │ │764 // │ │765 │ │766 + try evt.Patch.update(.xit, repo_opts, io, allocator, &target, &patch_id, .{ .status = .closed }, author);│ │767 + try std.testing.expectError(error.PatchClosed, pch.merge(repo_opts, io, allocator, repos_dir, &target, .{│ │768 + .id = patch_id_hex, │ │769 + .revision = merge_revision, │ │770 + .author = author, │ │771 + .timestamp = 5, │ │772 + })); │ │773 + try evt.Patch.update(.xit, repo_opts, io, allocator, &target, &patch_id, .{ .status = .open }, author); │ │774 + │ │775 const selected_oid = switch (merge_revision) { │ │776 .squash => squash_oid, │ │777 .source => source_oid, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │778 }; │ │770 - try pch.merge(repo_opts, io, allocator, repos_dir, &target, .{ │ │779 + try pch.mergeAndRemoveFork(repo_opts, io, allocator, repos_dir, &admin, &target, .{│ │780 .id = patch_id_hex, │ │781 .revision = merge_revision, │ │782 .author = author, │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │790 const merged = (try evt.Patch.readById(Repo.DB, repo_opts.hash, merged_moment, &arena, &patch_id)) orelse return error.NotFound; │ │791 try std.testing.expectEqual(.merged, merged.event.status); │ │792 try std.testing.expect(null != try evt.PatchRev.readById(Repo.DB, repo_opts.hash, merged_moment, &arena, &first_patchrev_id)); │ │793 + │ │794 + _ = arena.reset(.retain_capacity); │ │795 + const removed_fork = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, try evt.currentMoment(evt.admin_repo_opts, &admin), &arena, &patch_id)) orelse return error.NotFound;│ │796 + try std.testing.expect(removed_fork.removed); │ │797 } │ │798 │ │799 fn indexedForkCount( │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1152 }); │ │1153 │ │1154 if (status == .merged) { │ │1155 - try pch.merge(.{}, io, allocator, repos_path, target_repo, .{ │ │1155 + try pch.mergeAndRemoveFork(.{}, io, allocator, repos_path, admin_repo, target_repo, .{│ │1156 .id = patch_hex, │ │1157 .revision = .source, │ │1158 .author = patch_author, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1159 .timestamp = timestamp + 7, │ │1160 }); │ │1161 - try fork.remove(io, allocator, repos_path, admin_repo, &patch_hex, user_id, patch_author); │ │1161 } else if (status != .open) { │ │1162 try evt.Patch.update(.xit, .{}, io, allocator, target_repo, &patch_ids[i], .{ .status = status }, patch_author);│ │1163 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │177 │ │178 pub const IssuesView = enum { open, closed, tags, new, edit, description, new_comment, edit_comment, remove, conflicts, resolve }; │ │179 │ │180 - pub const PatchesView = enum { open, closed, merged, tags, new, drafts, edit, publish, description, new_comment, edit_comment, remove, conflicts, resolve }; │ │180 + pub const PatchesView = enum { open, closed, merged, tags, new, drafts, edit, publish, merge, description, new_comment, edit_comment, remove, conflicts, resolve };│ │181 │ │182 pub const DiscussionsView = enum { recent, tags, new, edit, description, new_comment, edit_comment, remove }; │ │183 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │589 var route = repoPatchesRoute(identity, .open, "", selected) orelse return null; │ │590 switch (route) { │ │591 .repo_patches => |*patch| patch.view = .publish, │ │592 + else => unreachable, │ │593 + } │ │594 + return route; │ │595 + } │ │596 + │ │597 + pub fn repoPatchMergeRoute(identity: []const u8, selected: []const u8) ?RoutablePage {│ │598 + if (selected.len == 0) return null; │ │599 + var route = repoPatchesRoute(identity, .open, "", selected) orelse return null; │ │600 + switch (route) { │ │601 + .repo_patches => |*patch| patch.view = .merge, │ │602 else => unreachable, │ │603 } │ │604 return route; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯