╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │73 - var source_oid: ?[]const u8 = null; │ │74 - const draft_exists = if (std.Io.Dir.accessAbsolute(io, draft_path, .{}))│ │75 + const path_exists = if (std.Io.Dir.accessAbsolute(io, path, .{})) │ │76 true │ │77 else |err| switch (err) { │ │78 error.FileNotFound => false, │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 79 else => |other| return other, │ │ 80 }; │ │ 80 - if (draft_exists) { │ │ 81 - if (existing == null) return error.InvalidPatchDraft; │ │ 82 - if (existing) |record| { │ │ 83 - if (!record.removed and std.mem.eql(u8, record.event.target, input.target)) source_oid = record.event.source_oid; │ │ 84 - } │ │ 85 - } else { │ │ 86 - try std.Io.Dir.cwd().createDirPath(io, std.fs.path.dirname(draft_path) orelse return error.InvalidPatchDraft); │ │ 87 - created_repo = true; │ │ 88 - var repo = try rp.Repo(.xit, repo_opts).init(io, allocator, .{ .path = draft_path, .create_default_branch = ref.name }); │ │ 81 + if (existing != null and !path_exists) return error.PatchDataUnavailable; │ │ 82 + if (existing == null and path_exists) return error.InvalidPatchDraft; │ │ 83 + │ │ 84 + if (path_exists) { │ │ 85 + var repo = try rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = path }); │ │ 86 defer repo.deinit(io, allocator); │ │ 90 - try repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" }); │ │ 87 + const moment = try evt.currentMoment(repo_opts, &repo); │ │ 88 + const patch = (try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, moment, &arena, &fork_id)) orelse return error.InvalidPatchDraft; │ │ 89 + if (patch.removed or │ │ 90 + !std.mem.eql(u8, patch.event.title, input.title) or │ │ 91 + !std.mem.eql(u8, patch.event.description, input.description) or │ │ 92 + !std.mem.eql(u8, patch.event.tags, input.tags)) return error.InvalidPatchDraft; │ │ 93 + return path; │ │ 94 } │ │ 95 │ │ 93 - const unchanged = if (existing) |record| │ │ 94 - !record.removed and std.mem.eql(u8, record.event.target, input.target) and │ │ 95 - evt.fieldEqual(?[]const u8, record.event.source_oid, source_oid) │ │ 96 - else │ │ 97 - false; │ │ 98 - if (!unchanged) { │ │ 99 - try evt.consume(.xit, evt.admin_repo_opts, io, allocator, admin_repo, evt.events_ref, &.{.{ │ │100 - .id = input.id, │ │101 - .timestamp = input.timestamp, │ │102 - .author = input.author, │ │103 - .event = .{ .fork = .{ │ │104 - .user_id = &input.user_id, │ │105 - .repo_id = &input.repo_id, │ │106 - .target = input.target, │ │107 - .source_oid = source_oid, │ │108 - } }, │ │109 - }}); │ │110 - } │ │111 - return draft_path; │ │ 96 + try std.Io.Dir.cwd().createDirPath(io, std.fs.path.dirname(path) orelse return error.InvalidPatchDraft); │ │ 97 + errdefer std.Io.Dir.cwd().deleteTree(io, path) catch {}; │ │ 98 + var repo = try rp.Repo(.xit, repo_opts).init(io, allocator, .{ .path = path, .create_default_branch = ref.name }); │ │ 99 + defer repo.deinit(io, allocator); │ │100 + try repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" }); │ │101 + try repo.addConfig(io, allocator, .{ .name = "receive.denydeletes", .value = "true" }); │ │102 + │ │103 + try evt.consume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &.{.{ │ │104 + .id = input.id, │ │105 + .timestamp = input.timestamp, │ │106 + .author = input.author, │ │107 + .event = .{ .patch = .{ │ │108 + .title = input.title, │ │109 + .description = input.description, │ │110 + .tags = input.tags, │ │111 + } }, │ │112 + }}); │ │113 + try evt.consume(.xit, evt.admin_repo_opts, io, allocator, admin_repo, evt.events_ref, &.{.{ │ │114 + .id = input.id, │ │115 + .timestamp = input.timestamp, │ │116 + .author = input.author, │ │117 + .event = .{ .fork = .{ │ │118 + .user_id = &input.user_id, │ │119 + .repo_id = &input.repo_id, │ │120 + } }, │ │121 + }}); │ │122 + return path; │ │123 } │ │124 │ │114 -pub fn recordPush( │ │125 +pub fn receivePack( │ │126 + comptime repo_opts: rp.RepoOpts(.xit), │ │127 io: std.Io, │ │128 allocator: std.mem.Allocator, │ │117 - admin_repo: *rp.Repo(.xit, evt.admin_repo_opts), │ │129 + fork_repo: *rp.Repo(.xit, repo_opts), │ │130 + target_repo: *rp.Repo(.xit, repo_opts), │ │131 id: *const [evt.event_id_size * 2]u8, │ │119 - target: []const u8, │ │120 - source_oid: []const u8, │ │132 + target_branch: []const u8, │ │133 author: evt.CommitAuthor, │ │134 timestamp: u64, │ │135 + reader: *std.Io.Reader, │ │136 + writer: *std.Io.Writer, │ │137 ) !void { │ │124 - const fork_id = try evt.parseEventId(id); │ │125 - if (!xit.ref.validateName(target)) return error.InvalidTarget; │ │138 + if (!rf.validateName(target_branch)) return error.InvalidTarget; │ │139 + const patch_id = try evt.parseEventId(id); │ │140 var arena = std.heap.ArenaAllocator.init(allocator); │ │141 defer arena.deinit(); │ │128 - const moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); │ │129 - const record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.InvalidPatchDraft; │ │130 - if (record.removed) return error.InvalidPatchDraft; │ │131 - try evt.consume(.xit, evt.admin_repo_opts, io, allocator, admin_repo, evt.events_ref, &.{.{ │ │142 + │ │143 + const fork_moment = evt.currentMoment(repo_opts, fork_repo) catch return error.PatchDataUnavailable; │ │144 + const fork_patch = (try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena, &patch_id)) orelse return error.PatchDataUnavailable; │ │145 + │ │146 + const target_moment = evt.currentMoment(repo_opts, target_repo) catch |err| switch (err) { │ │147 + error.NotFound => null, │ │148 + else => |other| return other, │ │149 + }; │ │150 + const target_patch = if (target_moment) |moment| │ │151 + try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, moment, &arena, &patch_id) │ │152 + else │ │153 + null; │ │154 + if (fork_patch.removed and target_patch == null) return error.PatchDataUnavailable; │ │155 + if (target_patch) |patch| { │ │156 + if (!std.mem.eql(u8, patch.author_email orelse "", author.email)) return error.PatchDataUnavailable; │ │157 + } │ │158 + const published = target_patch != null; │ │159 + const title = if (target_patch) |patch| patch.event.title else fork_patch.event.title; │ │160 + │ │161 + const target_oid = (try target_repo.readRef(io, .{ .kind = .head, .name = target_branch })) orelse return error.TargetNotFound; │ │162 + const newest = try evt.PatchRev.readNewest(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena); │ │163 + const target_ref = try std.fmt.allocPrint(arena.allocator(), "refs/heads/{s}", .{target_branch}); │ │164 + var revision_id_maybe: ?[evt.event_id_size]u8 = null; │ │165 + │ │166 + const DB = rp.Repo(.xit, repo_opts).DB; │ │167 + const State = rp.Repo(.xit, repo_opts).State; │ │168 + const Ctx = struct { │ │169 + core: *rp.Repo(.xit, repo_opts).Core, │ │170 + target_core: *rp.Repo(.xit, repo_opts).Core, │ │171 + io: std.Io, │ │172 + allocator: std.mem.Allocator, │ │173 + reader: *std.Io.Reader, │ │174 + writer: *std.Io.Writer, │ │175 + patch_id: [evt.event_id_size * 2]u8, │ │176 + patch: evt.Patch.Record, │ │177 + published: bool, │ │178 + target_oid: [hash.hexLen(repo_opts.hash)]u8, │ │179 + target_ref: []const u8, │ │180 + title: []const u8, │ │181 + author: evt.CommitAuthor, │ │182 + timestamp: u64, │ │183 + newest: ?evt.PatchRev.WithId, │ │184 + revision_id_maybe: *?[evt.event_id_size]u8, │ │185 + │ │186 + pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │187 + var moment = try DB.HashMap(.read_write).init(cursor.*); │ │188 + const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │189 + try xit.net_server_receive_pack.run(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.reader, ctx.writer, .{ .allowed_ref = ref_path }); │ │190 + │ │191 + const source_oid = (try rf.readRecur(.xit, repo_opts, state.readOnly(), ctx.io, .{ .ref = ref })) orelse return error.CancelTransaction; │ │192 + var target_repo_moment = try ctx.target_core.latestMoment(); │ │193 + const target_state = State(.read_only){ .core = ctx.target_core, .extra = .{ .moment = &target_repo_moment } }; │ │194 + var objects = try obj.ObjectIterator(.xit, repo_opts).init(target_state, ctx.io, ctx.allocator, .{ .kind = .all }); │ │195 + defer objects.deinit(); │ │196 + try objects.include(&ctx.target_oid); │ │197 + try obj.copyFromObjectIterator(.xit, repo_opts, state, .xit, repo_opts, &objects, ctx.io, null); │ │198 + │ │199 + const base_oid = try mrg.commonAncestor(.xit, repo_opts, state.readOnly(), ctx.io, ctx.allocator, &ctx.target_oid, &source_oid); │ │200 + const existing_revision = if (ctx.newest) |latest| │ │201 + if (std.mem.eql(u8, latest.record.event.base_oid, &base_oid) and │ │202 + std.mem.eql(u8, latest.record.event.source_oid, &source_oid) and │ │203 + std.mem.eql(u8, latest.record.event.target_ref, ctx.target_ref)) latest else null │ │204 + else │ │205 + null; │ │206 + │ │207 + var events: [2]evt.EventWithId = undefined; │ │208 + var event_count: usize = 0; │ │209 + var base_tree_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │210 + var head_tree_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │211 + var tree_entries: [2]evt.EventTreeEntry = undefined; │ │212 + if (existing_revision) |latest| { │ │213 + if (ctx.published) ctx.revision_id_maybe.* = latest.id; │ │214 + } else { │ │215 + var base_object = try obj.Object(.xit, repo_opts).initCommit(state.readOnly(), ctx.io, ctx.allocator, &base_oid); │ │216 + defer base_object.deinit(); │ │217 + var source_object = try obj.Object(.xit, repo_opts).initCommit(state.readOnly(), ctx.io, ctx.allocator, &source_oid); │ │218 + defer source_object.deinit(); │ │219 + base_tree_oid = base_object.content.commit.tree; │ │220 + head_tree_oid = source_object.content.commit.tree; │ │221 + │ │222 + var revision_id: [evt.event_id_size]u8 = undefined; │ │223 + ctx.io.random(&revision_id); │ │224 + const revision_hex = std.fmt.bytesToHex(revision_id, .lower); │ │225 + tree_entries = .{ │ │226 + .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, │ │227 + .{ .tree = .{ .name = "head", .oid = &head_tree_oid } }, │ │228 + }; │ │229 + events[event_count] = .{ │ │230 + .id = revision_hex, │ │231 + .timestamp = ctx.timestamp, │ │232 + .author = ctx.author, │ │233 + .tree_entries = &tree_entries, │ │234 + .event = .{ .patchrev = .{ │ │235 + .base_oid = &base_oid, │ │236 + .source_oid = &source_oid, │ │237 + .target_ref = ctx.target_ref, │ │238 + .message = ctx.title, │ │239 + } }, │ │240 + }; │ │241 + event_count += 1; │ │242 + ctx.revision_id_maybe.* = revision_id; │ │243 + │ │244 + if (!ctx.published) { │ │245 + events[event_count] = .{ │ │246 + .id = ctx.patch_id, │ │247 + .timestamp = ctx.timestamp, │ │248 + .author = ctx.author, │ │249 + .event = .{ .patch = .{ │ │250 + .title = ctx.patch.event.title, │ │251 + .description = ctx.patch.event.description, │ │252 + .tags = ctx.patch.event.tags, │ │253 + .target_patch_id = ctx.patch.event.target_patch_id, │ │254 + .patchrev_id = revision_hex, │ │255 + } }, │ │256 + }; │ │257 + event_count += 1; │ │258 + } │ │259 + } │ │260 + │ │261 + if (ctx.published and !ctx.patch.removed) { │ │262 + events[event_count] = .{ │ │263 + .id = ctx.patch_id, │ │264 + .timestamp = ctx.timestamp, │ │265 + .author = ctx.author, │ │266 + .event = .{ .patch = null }, │ │267 + }; │ │268 + event_count += 1; │ │269 + } │ │270 + if (event_count > 0) { │ │271 + try evt.commitEvents(.xit, repo_opts, state, ctx.io, ctx.allocator, evt.events_ref, events[0..event_count], null); │ │272 + if (!try evt.consumeInTransaction(.xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, evt.events_ref)) return error.CancelTransaction;│ │273 + } │ │274 + try xit.undo.writeMessage(repo_opts, state, .push); │ │275 + try ctx.core.chunk_store_file.sync(ctx.io); │ │276 + } │ │277 + }; │ │278 + │ │279 + try fork_repo.core.db_file.lock(io, .exclusive); │ │280 + defer fork_repo.core.db_file.unlock(io); │ │281 + const history = try DB.ArrayList(.read_write).init(fork_repo.core.db.rootCursor()); │ │282 + history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │283 + .core = &fork_repo.core, │ │284 + .target_core = &target_repo.core, │ │285 + .io = io, │ │286 + .allocator = allocator, │ │287 + .reader = reader, │ │288 + .writer = writer, │ │289 + .patch_id = id.*, │ │290 + .patch = fork_patch, │ │291 + .published = published, │ │292 + .target_oid = target_oid, │ │293 + .target_ref = target_ref, │ │294 + .title = title, │ │295 + .author = author, │ │296 + .timestamp = timestamp, │ │297 + .newest = newest, │ │298 + .revision_id_maybe = &revision_id_maybe, │ │299 + }) catch |err| switch (err) { │ │300 + error.CancelTransaction => {}, │ │301 + else => |other| return other, │ │302 + }; │ │303 + try writer.flush(); │ │304 + │ │305 + const revision_id = revision_id_maybe orelse return; │ │306 + if (!published) return; │ │307 + var update_arena = std.heap.ArenaAllocator.init(allocator); │ │308 + defer update_arena.deinit(); │ │309 + const update_moment = evt.currentMoment(repo_opts, target_repo) catch return; │ │310 + const published_patch = (evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, update_moment, &update_arena, &patch_id) catch return) orelse return; │ │311 + if (published_patch.removed) return; │ │312 + const revision_hex = std.fmt.bytesToHex(revision_id, .lower); │ │313 + if (published_patch.event.patchrev_id) |*published_revision| { │ │314 + if (std.mem.eql(u8, published_revision, &revision_hex)) return; │ │315 + } │ │316 + evt.consume(.xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{ │ │317 .id = id.*, │ │318 .timestamp = timestamp, │ │319 .author = author, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │135 - .event = .{ .fork = .{ │ │136 - .user_id = record.event.user_id, │ │137 - .repo_id = record.event.repo_id, │ │138 - .target = target, │ │139 - .source_oid = source_oid, │ │320 + .event = .{ .patch = .{ │ │321 + .title = published_patch.event.title, │ │322 + .description = published_patch.event.description, │ │323 + .tags = published_patch.event.tags, │ │324 + .target_patch_id = published_patch.event.target_patch_id,│ │325 + .patchrev_id = revision_hex, │ │326 } }, │ │141 - }}); │ │327 + }}) catch {}; │ │328 } │ │329 │ │330 pub const PublishInput = struct { │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────╮ │331 id: [evt.event_id_size * 2]u8, │ │332 user_id: [evt.event_id_size]u8,│ │333 repo_id: [evt.event_id_size]u8,│ │148 - title: []const u8, │ │149 - tags: []const u8, │ │150 - description: []const u8, │ │334 author: evt.CommitAuthor, │ │335 timestamp: u64, │ │336 }; │ ╰────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │341 allocator: std.mem.Allocator, │ │342 admin_repo: *rp.Repo(.xit, evt.admin_repo_opts), │ │343 target_repo: *rp.Repo(.xit, repo_opts), │ │161 - draft_path: []const u8, │ │344 + path: []const u8, │ │345 input: PublishInput, │ │346 ) !void { │ │164 - if (!evt.Patch.fieldsValid(input.title, input.tags)) return error.InvalidPatch; │ │347 const patch_id = try evt.parseEventId(&input.id); │ │166 - │ │348 var arena = std.heap.ArenaAllocator.init(allocator); │ │349 defer arena.deinit(); │ │350 + │ │351 const admin_moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); │ │352 const fork_record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &patch_id)) orelse return error.InvalidPatchDraft;│ │353 if (fork_record.removed or │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │354 !std.mem.eql(u8, fork_record.event.user_id, &input.user_id) or │ │355 !std.mem.eql(u8, fork_record.event.repo_id, &input.repo_id)) return error.InvalidPatchDraft; │ │356 const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &input.user_id)) orelse return error.InvalidPatchDraft; │ │175 - if (user.removed or !std.mem.eql(u8, user.event.name, input.author.name) or !std.mem.eql(u8, user.event.email, input.author.email)) return error.InvalidPatchDraft;│ │176 - const target_name = fork_record.event.target; │ │177 - const recorded_source_oid = fork_record.event.source_oid orelse return error.PatchNotPushed; │ │357 + if (user.removed or │ │358 + !std.mem.eql(u8, user.event.name, input.author.name) or │ │359 + !std.mem.eql(u8, user.event.email, input.author.email)) return error.InvalidPatchDraft; │ │360 │ │179 - const moment_maybe = evt.currentMoment(repo_opts, target_repo) catch |err| switch (err) { │ │361 + var fork_repo = rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = path }) catch return error.PatchDataUnavailable; │ │362 + defer fork_repo.deinit(io, allocator); │ │363 + const fork_moment = evt.currentMoment(repo_opts, &fork_repo) catch return error.PatchDataUnavailable; │ │364 + const local_patch = (try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena, &patch_id)) orelse return error.PatchDataUnavailable;│ │365 + │ │366 + const target_moment = evt.currentMoment(repo_opts, target_repo) catch |err| switch (err) { │ │367 error.NotFound => null, │ │368 else => |other| return other, │ │369 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │183 - const existing = if (moment_maybe) |moment| try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, moment, &arena, &patch_id) else null;│ │370 + const existing = if (target_moment) |moment| │ │371 + try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, moment, &arena, &patch_id) │ │372 + else │ │373 + null; │ │374 if (existing) |record| { │ │375 if (record.removed or !std.mem.eql(u8, record.author_email orelse "", input.author.email)) return error.InvalidPatch; │ │376 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │377 + if (local_patch.removed) { │ │378 + if (existing == null) return error.PatchDataUnavailable; │ │379 + return; │ │380 + } │ │381 │ │188 - var stage = try rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = draft_path }); │ │189 - defer stage.deinit(io, allocator); │ │190 - const source_oid = (try stage.readRef(io, ref)) orelse return error.PatchNotPushed; │ │191 - if (!std.mem.eql(u8, recorded_source_oid, &source_oid)) return error.InvalidPatchDraft; │ │382 + const revision_hex = local_patch.event.patchrev_id orelse return error.PatchNotPushed; │ │383 + const revision_id = try evt.parseEventId(&revision_hex); │ │384 + const revision = (try evt.PatchRev.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena, &revision_id)) orelse return error.PatchNotPushed;│ │385 + if (revision.removed) return error.PatchNotPushed; │ │386 + const metadata = existing orelse local_patch; │ │387 │ │193 - const target_oid = (try target_repo.readRef(io, .{ .kind = .head, .name = target_name })) orelse return error.TargetNotFound; │ │194 - var stage_moment = try stage.core.latestMoment(); │ │195 - const stage_state = rp.Repo(.xit, repo_opts).State(.read_only){ .core = &stage.core, .extra = .{ .moment = &stage_moment } }; │ │196 - var objects = try obj.ObjectIterator(.xit, repo_opts).init(stage_state, io, allocator, .{ .kind = .all }); │ │197 - defer objects.deinit(); │ │198 - try objects.include(&source_oid); │ │199 - try target_repo.copyObjects(.xit, repo_opts, &objects, io, null); │ │200 - │ │201 - var target_moment = try target_repo.core.latestMoment(); │ │202 - const target_state = rp.Repo(.xit, repo_opts).State(.read_only){ .core = &target_repo.core, .extra = .{ .moment = &target_moment } }; │ │203 - const base_oid = try mrg.commonAncestor(.xit, repo_opts, target_state, io, allocator, &target_oid, &source_oid); │ │204 - var base_object = try obj.Object(.xit, repo_opts).initCommit(target_state, io, allocator, &base_oid); │ │205 - defer base_object.deinit(); │ │206 - var source_object = try obj.Object(.xit, repo_opts).initCommit(target_state, io, allocator, &source_oid); │ │207 - defer source_object.deinit(); │ │208 - const base_tree_oid = base_object.content.commit.tree; │ │209 - const head_tree_oid = source_object.content.commit.tree; │ │210 - │ │211 - var message: std.ArrayList(u8) = .empty; │ │212 - defer message.deinit(allocator); │ │213 - try source_object.readMessage(allocator, &message, .limited(10_000)); │ │214 - │ │215 - const target_ref = try std.fmt.allocPrint(arena.allocator(), "refs/heads/{s}", .{target_name}); │ │216 - var revision_changed = true; │ │217 - var patchrev_id: [evt.event_id_size]u8 = undefined; │ │218 - if (existing) |record| { │ │219 - patchrev_id = try evt.parseEventId(&record.event.patchrev_id); │ │220 - if (std.mem.eql(u8, record.event.target_ref, target_ref)) { │ │221 - const moment = moment_maybe orelse return error.PatchNotFound; │ │222 - if (try evt.PatchRev.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, moment, &arena, &patchrev_id)) |revision| { │ │223 - revision_changed = !std.mem.eql(u8, revision.event.base_oid, &base_oid) or !std.mem.eql(u8, revision.event.source_oid, &source_oid); │ │224 - } │ │225 - } │ │226 - } │ │227 - if (revision_changed) io.random(&patchrev_id); │ │228 - const patchrev_hex = std.fmt.bytesToHex(patchrev_id, .lower); │ │229 - │ │230 - const tree_entries = [_]evt.EventTreeEntry{ │ │231 - .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, │ │232 - .{ .tree = .{ .name = "head", .oid = &head_tree_oid } }, │ │233 - }; │ │234 - const patch_event = evt.EventWithId{ │ │388 + try evt.consume(.xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{ │ │389 .id = input.id, │ │390 .timestamp = input.timestamp, │ │391 .author = input.author, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │392 .event = .{ .patch = .{ │ │239 - .title = input.title, │ │240 - .description = input.description, │ │241 - .tags = input.tags, │ │242 - .target_ref = target_ref, │ │243 - .target_patch_id = if (existing) |record| record.event.target_patch_id else null, │ │244 - .patchrev_id = patchrev_hex, │ │393 + .title = metadata.event.title, │ │394 + .description = metadata.event.description, │ │395 + .tags = metadata.event.tags, │ │396 + .target_patch_id = metadata.event.target_patch_id, │ │397 + .patchrev_id = revision_hex, │ │398 } }, │ │246 - }; │ │247 - if (revision_changed) { │ │248 - try evt.consume(.xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{ │ │249 - .{ │ │250 - .id = patchrev_hex, │ │251 - .timestamp = input.timestamp, │ │252 - .author = input.author, │ │253 - .tree_entries = &tree_entries, │ │254 - .event = .{ .patchrev = .{ .base_oid = &base_oid, .source_oid = &source_oid, .message = message.items } },│ │255 - }, │ │256 - patch_event, │ │257 - }); │ │258 - } else { │ │259 - try evt.consume(.xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{patch_event}); │ │260 - } │ │399 + }}); │ │400 + │ │401 + try evt.consume(.xit, repo_opts, io, allocator, &fork_repo, evt.events_ref, &.{.{ │ │402 + .id = input.id, │ │403 + .timestamp = input.timestamp, │ │404 + .author = input.author, │ │405 + .event = .{ .patch = null }, │ │406 + }}); │ │407 } │ │408 │ │409 // delete first so a failed tombstone can be retried safely │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │423 const record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.InvalidPatchDraft;│ │424 if (!std.mem.eql(u8, record.event.user_id, user_id)) return error.InvalidPatchDraft; │ │425 │ │280 - const draft_path = try forkPath(allocator, repo_root_path, id); │ │281 - defer allocator.free(draft_path); │ │282 - try std.Io.Dir.cwd().deleteTree(io, draft_path); │ │426 + const path = try forkPath(allocator, repo_root_path, id); │ │427 + defer allocator.free(path); │ │428 + try std.Io.Dir.cwd().deleteTree(io, path); │ │429 if (!record.removed) try evt.remove(.xit, evt.admin_repo_opts, io, allocator, admin_repo, &fork_id, .fork, author); │ │430 } │ │431 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯