╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │731 if (existing) |record| { │ │732 if (record.removed or !std.mem.eql(u8, record.author_email orelse "", input.author.email)) return error.InvalidPatch;│ │733 } │ │734 + │ │735 + // publish the selected revision unless the draft was already consumed │ │736 if (local_patch.removed) { │ │737 if (existing == null) return error.PatchDataUnavailable; │ │738 } else { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │745 } │ │746 if (existing == null) patch.status = .open; │ │747 │ │748 + // write the published patch to the target repository │ │749 try evt.consume(.server, .repo, .xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{│ │750 .id = input.id, │ │751 .timestamp = input.timestamp, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │753 .event = .{ .patch = patch }, │ │754 }}); │ │755 │ │756 + // remove the consumed draft from the fork │ │757 try evt.consume(.server, .fork, .xit, repo_opts, io, allocator, &fork_repo, evt.events_ref, &.{.{│ │758 .id = input.id, │ │759 .timestamp = input.timestamp, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │762 }}); │ │763 } │ │764 │ │765 + // mark the fork as published in the administration repository│ │766 if (fork_record.event.stage == .draft) { │ │767 var published = fork_record.event; │ │768 published.stage = .publish; │ ╰───────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │783 fork_path: []const u8, │ │784 input: EditDraftInput, │ │785 ) !bool { │ │786 + // validate the submitted fields before opening repositories │ │787 if (!evt.Patch.fieldsValid(input.title, input.tags)) return error.InvalidFields; │ │788 if (!evt.Patch.branchValid(input.target_branch)) return error.InvalidTargetBranch;│ │789 const patch_id = try evt.parseEventId(&input.id); │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │790 var arena = std.heap.ArenaAllocator.init(allocator); │ │791 defer arena.deinit(); │ │792 │ │581 - const admin_moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); │ │582 - const fork_record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &patch_id)) orelse return false; │ │583 - if (fork_record.event.stage != .draft) return false; │ │584 - if (fork_record.removed or │ │585 - !std.mem.eql(u8, fork_record.event.user_id, &input.user_id) or │ │586 - !std.mem.eql(u8, fork_record.event.repo_id, &input.repo_id)) return error.InvalidPatchDraft; │ │587 - const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &input.user_id)) orelse return error.InvalidPatchDraft; │ │588 - if (user.removed or │ │589 - !std.mem.eql(u8, user.event.name, input.author.name) or │ │590 - !std.mem.eql(u8, user.event.email, input.author.email)) return error.InvalidPatchDraft; │ │793 + // validate the draft ownership and editing identity │ │794 + { │ │795 + const admin_moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); │ │796 + const fork_record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &patch_id)) orelse return false; │ │797 + if (fork_record.event.stage != .draft) return false; │ │798 + if (fork_record.removed or │ │799 + !std.mem.eql(u8, fork_record.event.user_id, &input.user_id) or │ │800 + !std.mem.eql(u8, fork_record.event.repo_id, &input.repo_id)) return error.InvalidPatchDraft; │ │801 + const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &input.user_id)) orelse return error.InvalidPatchDraft;│ │802 + if (user.removed or │ │803 + !std.mem.eql(u8, user.event.name, input.author.name) or │ │804 + !std.mem.eql(u8, user.event.email, input.author.email)) return error.InvalidPatchDraft; │ │805 + } │ │806 │ │807 + // read the draft patch from its fork │ │808 var fork_repo = rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = fork_path, .require_repo_root = true }) catch return error.PatchDataUnavailable; │ │809 defer fork_repo.deinit(io, allocator); │ │810 const fork_moment = evt.currentMoment(repo_opts, &fork_repo) catch return error.PatchDataUnavailable; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │811 const record = (try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena, &patch_id)) orelse return error.PatchDataUnavailable;│ │812 if (record.removed) return error.PatchDataUnavailable; │ │813 │ │814 + // apply the edits and invalidate a revision targeting another branch │ │815 var patch = record.event; │ │816 patch.title = input.title; │ │817 patch.tags = input.tags; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │818 patch.description = input.description; │ │819 if (!std.mem.eql(u8, patch.target_branch, input.target_branch)) patch.revision = null; │ │820 patch.target_branch = input.target_branch; │ │821 + │ │822 + // commit the updated draft metadata │ │823 try evt.consume(.server, .fork, .xit, repo_opts, io, allocator, &fork_repo, evt.events_ref, &.{.{│ │824 .id = input.id, │ │825 .timestamp = input.timestamp, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │863 const fork_path = try fork.forkPath(arena.allocator(), repo_root_path, &input.id); │ │864 var fork_repo_maybe: ?rp.Repo(.xit, repo_opts) = if (patch_record.event.source_branch != null) null else rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = fork_path, .require_repo_root = true }) catch return error.PatchDataUnavailable;│ │865 defer if (fork_repo_maybe) |*repo| repo.deinit(io, allocator); │ │866 + │ │867 // select the commit and merge identity │ │868 const selected_oid = oidArray(repo_opts.hash, switch (input.revision) { │ │869 .squash => selected.squash_oid, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │905 if (!std.mem.eql(u8, &newest.id, &revision_id) or !selected_revision.matches(newest.record)) return error.PatchOutOfDate; │ │906 } else if (!try branchCurrent(repo_opts, state.readOnly(), ctx.io, &check_arena, ctx.expected_patch)) return error.PatchOutOfDate; │ │907 │ │688 - const availability = try readMergeability(repo_opts, state.readOnly(), ctx.io, &check_arena, &ctx.patch_id, ctx.expected_patch); │ │689 - switch (availability.get(ctx.revision)) { │ │690 - .clean => {}, │ │691 - .conflict => return error.MergeConflict, │ │692 - .unknown => return error.MergeCheckUnavailable, │ │908 + // require a clean cached result for the selected merge style. │ │909 + { │ │910 + const availability = try readMergeability(repo_opts, state.readOnly(), ctx.io, &check_arena, &ctx.patch_id, ctx.expected_patch);│ │911 + switch (availability.get(ctx.revision)) { │ │912 + .clean => {}, │ │913 + .conflict => return error.MergeConflict, │ │914 + .unknown => return error.MergeCheckUnavailable, │ │915 + } │ │916 } │ │917 │ │918 // copy the selected commit and its dependencies │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯