╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │255 id: *const [evt.event_id_size]u8, │ │256 patch: evt.Patch, │ │257 ) !Mergeability { │ │258 + if (patch.source_branch != null and !try branchCurrent(repo_opts, state, io, arena, patch)) return .{}; │ │259 const target_oid = try rf.readRecur(.xit, repo_opts, state, io, .{ .ref = .{ .kind = .head, .name = patch.target_branch } }) orelse return .{};│ │260 const cached = try readMergeCheck(repo_opts, state.extra.moment.*, arena, id) orelse return .{}; │ │261 return if (cached.matches(patch, &target_oid)) cached.result else .{}; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │310 const DB = Repo.DB; │ │311 var arena = std.heap.ArenaAllocator.init(allocator); │ │312 defer arena.deinit(); │ │313 + const initial = try evt.Patch.readFromRepo(.xit, repo_opts, io, allocator, &arena, target_repo, id); │ │314 + if (initial) |record| if (record.removed or record.event.status.kind() != .open) return; │ │315 const repo_root_path = std.fs.path.dirname(target_repo.core.work_path) orelse "."; │ │316 const path = try fork.forkPath(arena.allocator(), repo_root_path, &std.fmt.bytesToHex(id.*, .lower)); │ │150 - var fork_repo_maybe: ?Repo = Repo.open(io, allocator, .{ .path = path, .require_repo_root = true }) catch |err| blk: { │ │317 + const branch_backed = if (initial) |record| record.event.source_branch != null else false; │ │318 + var fork_repo_maybe: ?Repo = if (branch_backed) null else Repo.open(io, allocator, .{ .path = path, .require_repo_root = true }) catch |err| blk: {│ │319 std.log.warn("mergeability fork unavailable: {s}", .{@errorName(err)}); │ │320 break :blk null; │ │321 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │349 const target_oid = try rf.readRecur(.xit, repo_opts, target_state, io, .{ .ref = .{ .kind = .head, .name = patch.target_branch } }); │ │350 var check = MergeCheck{ .revision = revision, .target_branch = patch.target_branch, .target_oid = if (target_oid) |*oid| oid else "" }; │ │351 const cached = try readMergeCheck(repo_opts, target_moment, &arena, id); │ │184 - const current = if (newest) |latest| std.mem.eql(u8, &std.fmt.bytesToHex(latest.id, .lower), &revision.id) and revision.matches(latest.record) else false; │ │352 + const current = if (patch.source_branch != null) try branchCurrent(repo_opts, target_state, io, &arena, patch) else if (newest) |latest| std.mem.eql(u8, &std.fmt.bytesToHex(latest.id, .lower), &revision.id) and revision.matches(latest.record) else false;│ │353 if (current and target_oid != null) { │ │354 if (cached) |value| if (value.matches(patch, check.target_oid) and value.result.source != .unknown and value.result.squash != .unknown) return; │ │355 const Ctx = struct { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────╮ │356 repo: *Repo, │ │189 - fork_repo: *Repo, │ │357 + fork_repo: ?*Repo, │ │358 io: std.Io, │ │359 allocator: std.mem.Allocator,│ │360 check: *MergeCheck, │ ╰──────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │362 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │363 var moment = try DB.HashMap(.read_write).init(cursor.*); │ │364 const state = Repo.State(.read_write){ .core = &ctx.repo.core, .extra = .{ .moment = &moment } }; │ │197 - var fork_moment = try ctx.fork_repo.core.latestMoment(); │ │198 - const fork_state = Repo.State(.read_only){ .core = &ctx.fork_repo.core, .extra = .{ .moment = &fork_moment } }; │ │199 - var objects = try obj.ObjectIterator(.xit, repo_opts).init(fork_state, ctx.io, ctx.allocator, .{ .kind = .all }); │ │200 - defer objects.deinit(); │ │365 const source = try oidArray(repo_opts.hash, ctx.check.revision.source_oid); │ │366 const squash = try oidArray(repo_opts.hash, ctx.check.revision.squash_oid); │ │203 - try objects.include(&source); │ │204 - try objects.include(&squash); │ │205 - try obj.copyFromObjectIterator(.xit, repo_opts, state, .xit, repo_opts, &objects, ctx.io, null); │ │367 + if (ctx.fork_repo) |repo| { │ │368 + var fork_moment = try repo.core.latestMoment(); │ │369 + const fork_state = Repo.State(.read_only){ .core = &repo.core, .extra = .{ .moment = &fork_moment } }; │ │370 + var objects = try obj.ObjectIterator(.xit, repo_opts).init(fork_state, ctx.io, ctx.allocator, .{ .kind = .all });│ │371 + defer objects.deinit(); │ │372 + try objects.include(&source); │ │373 + try objects.include(&squash); │ │374 + try obj.copyFromObjectIterator(.xit, repo_opts, state, .xit, repo_opts, &objects, ctx.io, null); │ │375 + } │ │376 const target = rf.Ref{ .kind = .head, .name = ctx.check.target_branch }; │ │377 ctx.check.result.source = checkCommit(repo_opts, state, ctx.io, ctx.allocator, &source, target); │ │378 ctx.check.result.squash = checkCommit(repo_opts, state, ctx.io, ctx.allocator, &squash, target); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │382 const history = try DB.ArrayList(.read_write).init(target_repo.core.db.rootCursor());│ │383 history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │384 .repo = target_repo, │ │215 - .fork_repo = if (fork_repo_maybe) |*repo| repo else unreachable, │ │385 + .fork_repo = if (fork_repo_maybe) |*repo| repo else null, │ │386 .io = io, │ │387 .allocator = allocator, │ │388 .check = &check, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │405 }; │ │406 const history = try DB.ArrayList(.read_write).init(target_repo.core.db.rootCursor()); │ │407 try history.appendContext(.{ .slot = try history.getSlot(-1) }, Save{ .id = id.*, .check = check }); │ │408 +} │ │409 + │ │410 +fn branchCurrent(comptime repo_opts: rp.RepoOpts(.xit), state: rp.Repo(.xit, repo_opts).State(.read_only), io: std.Io, arena: *std.heap.ArenaAllocator, patch: evt.Patch) !bool {│ │411 + const branch = patch.source_branch orelse return false; │ │412 + const selected = patch.revision orelse return false; │ │413 + const oid = (try rf.readRecur(.xit, repo_opts, state, io, .{ .ref = .{ .kind = .head, .name = branch } })) orelse return false; │ │414 + if (!std.mem.eql(u8, &oid, selected.source_oid)) return false; │ │415 + const events = try evt.currentMomentFromRepoMoment(repo_opts.hash, state.extra.moment.*); │ │416 + const revision = (try evt.PatchRev.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, events, arena, &try evt.parseEventId(&selected.id))) orelse return false; │ │417 + return selected.matches(revision); │ │418 } │ │419 │ │420 fn oidArray(comptime hash_kind: hash.HashKind, oid: []const u8) ![hash.hexLen(hash_kind)]u8 { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │609 │ │610 // open the fork │ │611 const fork_path = try fork.forkPath(arena.allocator(), repo_root_path, &input.id); │ │432 - var fork_repo = rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = fork_path, .require_repo_root = true }) catch return error.PatchDataUnavailable; │ │433 - defer fork_repo.deinit(io, allocator); │ │612 + 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;│ │613 + defer if (fork_repo_maybe) |*repo| repo.deinit(io, allocator); │ │614 // select the commit and merge identity │ │615 const merge_oid = oidArray(repo_opts.hash, switch (input.revision) { │ │616 .squash => selected.squash_oid, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────╮ │624 const State = rp.Repo(.xit, repo_opts).State;│ │625 const Ctx = struct { │ │626 core: *rp.Repo(.xit, repo_opts).Core, │ │447 - fork_repo: *rp.Repo(.xit, repo_opts), │ │627 + fork_repo: ?*rp.Repo(.xit, repo_opts), │ │628 io: std.Io, │ │629 allocator: std.mem.Allocator, │ │630 patch_id: [evt.event_id_size]u8, │ ╰──────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │646 // require the fork's newest revision while both repos are locked │ │647 const selected_revision = ctx.expected_patch.revision orelse unreachable; │ │648 const revision_id = try evt.parseEventId(&selected_revision.id); │ │469 - const fork_moment = try evt.currentMoment(repo_opts, ctx.fork_repo); │ │470 - const newest = (try evt.PatchRev.readNewest(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &check_arena)) orelse return error.PatchDataUnavailable; │ │471 - if (!std.mem.eql(u8, &newest.id, &revision_id) or │ │472 - !selected_revision.matches(newest.record)) return error.PatchOutOfDate; │ │649 + if (ctx.fork_repo) |repo| { │ │650 + const fork_moment = try evt.currentMoment(repo_opts, repo); │ │651 + const newest = (try evt.PatchRev.readNewest(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &check_arena)) orelse return error.PatchDataUnavailable;│ │652 + if (!std.mem.eql(u8, &newest.id, &revision_id) or !selected_revision.matches(newest.record)) return error.PatchOutOfDate; │ │653 + } else if (!try branchCurrent(repo_opts, state.readOnly(), ctx.io, &check_arena, ctx.expected_patch)) return error.PatchOutOfDate; │ │654 │ │655 const availability = try readMergeability(repo_opts, state.readOnly(), ctx.io, &check_arena, &ctx.patch_id, ctx.expected_patch); │ │656 switch (availability.get(ctx.revision)) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯