╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │361 const stacked_moment = try evt.currentMoment(repo_opts, &target); │ │362 const child = (try evt.Patch.readById(Repo.DB, repo_opts.hash, stacked_moment, &arena, &child_id)) orelse return error.NotFound; │ │363 var selected_patchrev_id: [evt.event_id_size]u8 = undefined; │ │366 - _ = try std.fmt.hexToBytes(&selected_patchrev_id, &child.event.patchrev_id); │ │364 + _ = try std.fmt.hexToBytes(&selected_patchrev_id, &(child.event.patchrev_id orelse return error.NotFound)); │ │365 const child_patchrev = (try evt.PatchRev.readById(Repo.DB, repo_opts.hash, stacked_moment, &arena, &selected_patchrev_id)) orelse return error.NotFound;│ │366 var child_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │367 @memcpy(&child_oid, child_patchrev.patch_oid); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │401 } │ │402 } │ │403 │ │406 -test "publish a staged patch" { │ │404 +test "patch lifecycle" { │ │405 const io = std.testing.io; │ │406 const allocator = std.testing.allocator; │ │409 - const temp_dir_name = "temp-staged-patch"; │ │407 + const temp_dir_name = "temp-patch-lifecycle"; │ │408 const cwd = std.Io.Dir.cwd(); │ │409 │ │410 if (cwd.openDir(io, temp_dir_name, .{})) |dir_value| {│ ╰───────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │426 defer allocator.free(admin_path); │ │427 const upstream_path = try std.fs.path.join(allocator, &.{ root, "upstream" });│ │428 defer allocator.free(upstream_path); │ │429 + const source_path = try std.fs.path.join(allocator, &.{ root, "source" }); │ │430 + defer allocator.free(source_path); │ │431 │ │432 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); │ │433 const user_id = evt.EventWithId.randomId(prng.random()); │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │476 } │ │477 var target = try Repo.clone(io, allocator, upstream_path, target_path, target_path, null, .{});│ │478 defer target.deinit(io, allocator); │ │479 - try target.addBranch(io, .{ .name = "next" }); │ │479 │ │480 // │ │482 - // push a proposed commit into a draft │ │481 + // create the patch draft in the fork │ │482 // │ │483 │ │484 const patch_id = evt.EventWithId.randomId(prng.random()); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │485 const patch_id_hex = std.fmt.bytesToHex(patch_id, .lower); │ │487 - const draft_path = try fork.forkPath(allocator, repos_dir, &patch_id_hex); │ │488 - defer allocator.free(draft_path); │ │489 - try evt.consume(.xit, evt.admin_repo_opts, io, allocator, &admin, evt.events_ref, &.{.{ │ │486 + const draft_path = try fork.create(repo_opts, io, allocator, repos_dir, &admin, .{ │ │487 .id = patch_id_hex, │ │491 - .timestamp = 2, │ │488 + .user_id = user_id, │ │489 + .repo_id = repo_id, │ │490 + .title = "add the answer", │ │491 + .tags = "enhancement", │ │492 + .description = "adds a reusable answer constant", │ │493 .author = author, │ │493 - .event = .{ .fork = .{ │ │494 - .user_id = &user_id, │ │495 - .repo_id = &repo_id, │ │496 - .target = "master", │ │497 - } }, │ │498 - }}); │ │499 - try cwd.createDirPath(io, std.fs.path.dirname(draft_path) orelse return error.InvalidPath); │ │500 - var draft = try Repo.clone(io, allocator, upstream_path, draft_path, draft_path, null, .{}); │ │501 - var source_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │494 + .timestamp = 2, │ │495 + }); │ │496 + defer allocator.free(draft_path); │ │497 + │ │498 + // │ │499 + // create and record the proposed revision in the fork │ │500 + // │ │501 + │ │502 + const base_oid = (try target.readRef(io, .{ .kind = .head, .name = "master" })) orelse return error.NotFound; │ │503 + const base_tree_oid = try commitTree(io, allocator, &target, &base_oid); │ │504 + var source = try Repo.clone(io, allocator, upstream_path, source_path, source_path, null, .{}); │ │505 + defer source.deinit(io, allocator); │ │506 + const source_oid = blk: { │ │507 + const file = try source.core.work_dir.createFile(io, "feature.zig", .{ .truncate = true }); │ │508 + defer file.close(io); │ │509 + try file.writeStreamingAll(io, "pub const answer = 42;\n"); │ │510 + try source.add(io, allocator, &.{"feature.zig"}); │ │511 + break :blk try source.commit(io, allocator, .{ .author = "alice <alice@example.test>", .message = "add feature", .timestamp = 2 }); │ │512 + }; │ │513 + const head_tree_oid = try commitTree(io, allocator, &source, &source_oid); │ │514 + const first_patchrev_id = evt.EventWithId.randomId(prng.random()); │ │515 + const first_patchrev_hex = std.fmt.bytesToHex(first_patchrev_id, .lower); │ │516 + const first_entries = [_]evt.EventTreeEntry{ │ │517 + .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, │ │518 + .{ .tree = .{ .name = "head", .oid = &head_tree_oid } }, │ │519 + }; │ │520 { │ │521 + var draft = try Repo.open(io, allocator, .{ .path = draft_path }); │ │522 defer draft.deinit(io, allocator); │ │504 - const file = try draft.core.work_dir.createFile(io, "feature.zig", .{ .truncate = true }); │ │505 - defer file.close(io); │ │506 - try file.writeStreamingAll(io, "pub const answer = 42;\n"); │ │507 - try draft.add(io, allocator, &.{"feature.zig"}); │ │508 - source_oid = try draft.commit(io, allocator, .{ .author = "alice <alice@example.test>", .message = "add feature", .timestamp = 2 }); │ │509 - try draft.addBranch(io, .{ .name = fork.ref.name }); │ │523 + var arena = std.heap.ArenaAllocator.init(allocator); │ │524 + defer arena.deinit(); │ │525 + │ │526 + const initial_moment = try evt.currentMoment(repo_opts, &draft); │ │527 + const initial_patch = (try evt.Patch.readById(Repo.DB, repo_opts.hash, initial_moment, &arena, &patch_id)) orelse return error.NotFound;│ │528 + try std.testing.expect(!initial_patch.removed); │ │529 + try std.testing.expectEqualStrings("add the answer", initial_patch.event.title); │ │530 + try std.testing.expectEqual(null, initial_patch.event.patchrev_id); │ │531 + try std.testing.expectEqual(null, try evt.PatchRev.readNewest(Repo.DB, repo_opts.hash, initial_moment, &arena)); │ │532 + │ │533 + var source_moment = try source.core.latestMoment(); │ │534 + const source_state = Repo.State(.read_only){ .core = &source.core, .extra = .{ .moment = &source_moment } }; │ │535 + var objects = try obj.ObjectIterator(.xit, repo_opts).init(source_state, io, allocator, .{ .kind = .all }); │ │536 + defer objects.deinit(); │ │537 + try objects.include(&source_oid); │ │538 + try draft.copyObjects(.xit, repo_opts, &objects, io, null); │ │539 + try evt.consume(.xit, repo_opts, io, allocator, &draft, evt.events_ref, &.{ │ │540 + .{ │ │541 + .id = first_patchrev_hex, │ │542 + .timestamp = 3, │ │543 + .author = author, │ │544 + .tree_entries = &first_entries, │ │545 + .event = .{ .patchrev = .{ │ │546 + .base_oid = &base_oid, │ │547 + .source_oid = &source_oid, │ │548 + .target_ref = "refs/heads/master", │ │549 + .message = "add the answer", │ │550 + } }, │ │551 + }, │ │552 + .{ │ │553 + .id = patch_id_hex, │ │554 + .timestamp = 3, │ │555 + .author = author, │ │556 + .event = .{ .patch = .{ │ │557 + .title = "add the answer", │ │558 + .tags = "enhancement", │ │559 + .description = "adds a reusable answer constant", │ │560 + .patchrev_id = first_patchrev_hex, │ │561 + } }, │ │562 + }, │ │563 + }); │ │564 + │ │565 + _ = arena.reset(.retain_capacity); │ │566 + const moment = try evt.currentMoment(repo_opts, &draft); │ │567 + const patch = (try evt.Patch.readById(Repo.DB, repo_opts.hash, moment, &arena, &patch_id)) orelse return error.NotFound; │ │568 + try std.testing.expect(!patch.removed); │ │569 + try std.testing.expectEqualStrings(&first_patchrev_hex, &(patch.event.patchrev_id orelse return error.NotFound)); │ │570 + try std.testing.expect(null != try evt.PatchRev.readById(Repo.DB, repo_opts.hash, moment, &arena, &first_patchrev_id)); │ │571 } │ │511 - try fork.recordPush(io, allocator, &admin, &patch_id_hex, "master", &source_oid, author, 3); │ │572 + try std.testing.expectError(error.NotFound, evt.currentMoment(repo_opts, &target)); │ │573 │ │574 // │ │514 - // publish the staged code and metadata │ │575 + // publish the metadata and revision pointer │ │576 // │ │577 │ │578 try fork.publish(repo_opts, io, allocator, &admin, &target, draft_path, .{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────╮ │579 .id = patch_id_hex, │ │580 .user_id = user_id, │ │581 .repo_id = repo_id, │ │521 - .title = "add the answer", │ │522 - .tags = "enhancement", │ │523 - .description = "adds a reusable answer constant",│ │582 .author = author, │ │583 .timestamp = 3, │ │584 }); │ ╰──────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │585 │ │586 var arena = std.heap.ArenaAllocator.init(allocator); │ │587 defer arena.deinit(); │ │530 - var moment = try evt.currentMoment(repo_opts, &target); │ │588 + const moment = try evt.currentMoment(repo_opts, &target); │ │589 const patch = (try evt.Patch.readById(Repo.DB, repo_opts.hash, moment, &arena, &patch_id)) orelse return error.NotFound; │ │532 - try std.testing.expectEqualStrings("refs/heads/master", patch.event.target_ref); │ │533 - const first_patchrev_id = try evt.parseEventId(&patch.event.patchrev_id); │ │534 - const first_patchrev = (try evt.PatchRev.readById(Repo.DB, repo_opts.hash, moment, &arena, &first_patchrev_id)) orelse return error.NotFound; │ │535 - try std.testing.expectEqualStrings("add feature", first_patchrev.event.message); │ │536 - const first_patchrev_hex = patch.event.patchrev_id; │ │590 + try std.testing.expectEqualStrings(&first_patchrev_hex, &(patch.event.patchrev_id orelse return error.NotFound)); │ │591 + try std.testing.expectEqual(null, try evt.PatchRev.readById(Repo.DB, repo_opts.hash, moment, &arena, &first_patchrev_id)); │ │592 │ │538 - var fork_arena = std.heap.ArenaAllocator.init(allocator); │ │539 - defer fork_arena.deinit(); │ │593 + _ = arena.reset(.retain_capacity); │ │594 const admin_moment = try evt.currentMoment(evt.admin_repo_opts, &admin); │ │541 - const published_fork = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &fork_arena, &patch_id)) orelse return error.NotFound;│ │595 + const published_fork = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &patch_id)) orelse return error.NotFound; │ │596 try std.testing.expect(!published_fork.removed); │ │543 - │ │544 - // │ │545 - // edit metadata without replacing the code revision │ │546 - // │ │547 - │ │548 - try fork.publish(repo_opts, io, allocator, &admin, &target, draft_path, .{ │ │549 - .id = patch_id_hex, │ │550 - .user_id = user_id, │ │551 - .repo_id = repo_id, │ │552 - .title = "add a reusable answer", │ │553 - .tags = "enhancement", │ │554 - .description = "adds a reusable answer constant", │ │555 - .author = author, │ │556 - .timestamp = 4, │ │557 - }); │ │558 - _ = arena.reset(.retain_capacity); │ │559 - moment = try evt.currentMoment(repo_opts, &target); │ │560 - const edited = (try evt.Patch.readById(Repo.DB, repo_opts.hash, moment, &arena, &patch_id)) orelse return error.NotFound; │ │561 - try std.testing.expectEqualStrings(&first_patchrev_hex, &edited.event.patchrev_id); │ │562 - const edited_patchrev_hex = edited.event.patchrev_id; │ │563 - │ │564 - // │ │565 - // retargeting creates another code revision │ │566 - // │ │567 - │ │568 - try fork.recordPush(io, allocator, &admin, &patch_id_hex, "next", &source_oid, author, 5); │ │569 - try fork.publish(repo_opts, io, allocator, &admin, &target, draft_path, .{ │ │570 - .id = patch_id_hex, │ │571 - .user_id = user_id, │ │572 - .repo_id = repo_id, │ │573 - .title = "add a reusable answer", │ │574 - .tags = "enhancement", │ │575 - .description = "adds a reusable answer constant", │ │576 - .author = author, │ │577 - .timestamp = 5, │ │578 - }); │ │579 - _ = arena.reset(.retain_capacity); │ │580 - moment = try evt.currentMoment(repo_opts, &target); │ │581 - const retargeted = (try evt.Patch.readById(Repo.DB, repo_opts.hash, moment, &arena, &patch_id)) orelse return error.NotFound; │ │582 - try std.testing.expectEqualStrings("refs/heads/next", retargeted.event.target_ref); │ │583 - try std.testing.expect(!std.mem.eql(u8, &edited_patchrev_hex, &retargeted.event.patchrev_id)); │ │597 + { │ │598 + var draft = try Repo.open(io, allocator, .{ .path = draft_path }); │ │599 + defer draft.deinit(io, allocator); │ │600 + _ = arena.reset(.retain_capacity); │ │601 + const draft_moment = try evt.currentMoment(repo_opts, &draft); │ │602 + const removed = (try evt.Patch.readById(Repo.DB, repo_opts.hash, draft_moment, &arena, &patch_id)) orelse return error.NotFound; │ │603 + try std.testing.expect(removed.removed); │ │604 + const revision = (try evt.PatchRev.readById(Repo.DB, repo_opts.hash, draft_moment, &arena, &first_patchrev_id)) orelse return error.NotFound; │ │605 + try std.testing.expectEqualStrings("refs/heads/master", revision.event.target_ref); │ │606 + try std.testing.expectEqual(hash.hexLen(repo_opts.hash), revision.event_oid.len); │ │607 + var event_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │608 + @memcpy(&event_oid, revision.event_oid); │ │609 + var repo_moment = try draft.core.latestMoment(); │ │610 + const state = Repo.State(.read_only){ .core = &draft.core, .extra = .{ .moment = &repo_moment } }; │ │611 + var event_object = try obj.Object(.xit, repo_opts).initCommit(state, io, allocator, &event_oid); │ │612 + defer event_object.deinit(); │ │613 + const trees = try evt.PatchRev.readTrees(.xit, repo_opts, state, io, allocator, &event_object.content.commit.tree); │ │614 + try std.testing.expectEqualStrings(revision.base_tree_oid, &trees.base); │ │615 + try std.testing.expectEqualStrings(revision.head_tree_oid, &trees.head); │ │616 + } │ │617 } │ │618 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯