╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────╮ │src/test/net.zig│ ╰────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │542 │ │543 const target_path = (try repoOnDiskPath(io, allocator, temp_dir_name, "target", true)).?; │ │544 defer allocator.free(target_path); │ │545 - var target_repo = try rp.Repo(.xit, .{}).init(io, allocator, .{ .path = target_path }); │ │546 - target_repo.deinit(io, allocator); │ │545 + { │ │546 + var target_repo = try rp.Repo(.xit, .{}).init(io, allocator, .{ .path = target_path }); │ │547 + defer target_repo.deinit(io, allocator); │ │548 + const file = try target_repo.core.work_dir.createFile(io, "main.txt", .{ .truncate = true }); │ │549 + defer file.close(io); │ │550 + try file.writeStreamingAll(io, "base contents\n"); │ │551 + try target_repo.add(io, allocator, &.{"main.txt"}); │ │552 + _ = try target_repo.commit(io, allocator, .{ .author = "admin <admin@example.test>", .message = "initial code" });│ │553 + } │ │554 │ │555 const other_path = (try repoOnDiskPath(io, allocator, temp_dir_name, "other", true)).?; │ │556 defer allocator.free(other_path); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │580 .id = fork_id_hex, │ │581 .user_id = user_id, │ │582 .repo_id = repo_id, │ │576 - .target = "master", │ │583 + .title = "add a feature", │ │584 + .description = "a draft patch", │ │585 + .tags = "enhancement", │ │586 .author = .{ .name = "admin", .email = "admin@example.test" },│ │587 .timestamp = 1, │ │588 }); │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │591 │ │592 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" }); │ │593 defer allocator.free(client_path); │ │585 - var client = try rp.Repo(.xit, .{ .is_test = true }).init(io, allocator, .{ .path = client_path, .create_default_branch = fork.ref.name });│ │594 + const ssh_cmd = (try sshCommand(true, allocator, cwd_path, temp_dir_name, port)).?; │ │595 + defer allocator.free(ssh_cmd); │ │596 + var client = try rp.Repo(.xit, .{ .is_test = true }).clone( │ │597 + io, │ │598 + allocator, │ │599 + "git@localhost:admin/target", │ │600 + client_path, │ │601 + client_path, │ │602 + null, │ │603 + .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } }, │ │604 + ); │ │605 defer client.deinit(io, allocator); │ │606 │ │607 const source_oid = blk: { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │614 │ │615 const remote_url = try std.fmt.allocPrint(allocator, "git@localhost:admin/target/patch:{s}/branch:master", .{&fork_id_hex}); │ │616 defer allocator.free(remote_url); │ │598 - try client.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); │ │599 - const ssh_cmd = (try sshCommand(true, allocator, cwd_path, temp_dir_name, port)).?; │ │600 - defer allocator.free(ssh_cmd); │ │601 - try client.push(io, allocator, "origin", fork.ref.name, false, .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } }); │ │617 + try client.addRemote(io, allocator, .{ .name = "patch", .value = remote_url }); │ │618 + try client.push(io, allocator, "patch", "master:patch", false, .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } }); │ │619 + │ │620 + var first_revision_id: [evt.event_id_size]u8 = undefined; │ │621 + { │ │622 + var draft = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = draft_path }); │ │623 + defer draft.deinit(io, allocator); │ │624 + var arena = std.heap.ArenaAllocator.init(allocator); │ │625 + defer arena.deinit(); │ │626 + const moment = try evt.currentMoment(.{}, &draft); │ │627 + const patch = (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, moment, &arena, &fork_id)) orelse return error.NotFound; │ │628 + try std.testing.expect(!patch.removed); │ │629 + first_revision_id = try evt.parseEventId(&(patch.event.patchrev_id orelse return error.NotFound)); │ │630 + const revision = (try evt.PatchRev.readById(evt.EventDB(.sha1), .sha1, moment, &arena, &first_revision_id)) orelse return error.NotFound;│ │631 + try std.testing.expectEqualStrings("refs/heads/master", revision.event.target_ref); │ │632 + try std.testing.expectEqualStrings(&source_oid, revision.event.source_oid); │ │633 + } │ │634 + │ │635 + // │ │636 + // publish, then push another revision │ │637 + // │ │638 │ │639 { │ │640 var admin = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = admin_path }); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │641 defer admin.deinit(io, allocator); │ │642 + var target = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = target_path }); │ │643 + defer target.deinit(io, allocator); │ │644 + try fork.publish(.{}, io, allocator, &admin, &target, draft_path, .{ │ │645 + .id = fork_id_hex, │ │646 + .user_id = user_id, │ │647 + .repo_id = repo_id, │ │648 + .author = .{ .name = "admin", .email = "admin@example.test" }, │ │649 + .timestamp = 2, │ │650 + }); │ │651 var arena = std.heap.ArenaAllocator.init(allocator); │ │652 defer arena.deinit(); │ │608 - const moment = try evt.currentMoment(evt.admin_repo_opts, &admin); │ │609 - const record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.NotFound; │ │610 - try std.testing.expect(!record.removed); │ │611 - try std.testing.expectEqualStrings("master", record.event.target); │ │612 - try std.testing.expectEqualStrings(&source_oid, record.event.source_oid orelse return error.NotFound); │ │653 + const moment = try evt.currentMoment(.{}, &target); │ │654 + const patch = (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, moment, &arena, &fork_id)) orelse return error.NotFound; │ │655 + try evt.consume(.xit, .{}, io, allocator, &target, evt.events_ref, &.{.{ │ │656 + .id = fork_id_hex, │ │657 + .timestamp = 3, │ │658 + .author = .{ .name = "admin", .email = "admin@example.test" }, │ │659 + .event = .{ .patch = .{ │ │660 + .title = "edited feature", │ │661 + .description = patch.event.description, │ │662 + .tags = patch.event.tags, │ │663 + .target_patch_id = patch.event.target_patch_id, │ │664 + .patchrev_id = patch.event.patchrev_id, │ │665 + } }, │ │666 + }}); │ │667 + } │ │668 + │ │669 + const second_source_oid = blk: { │ │670 + const file = try client.core.work_dir.createFile(io, "feature.txt", .{ .truncate = true }); │ │671 + defer file.close(io); │ │672 + try file.writeStreamingAll(io, "updated fork contents\n"); │ │673 + try client.add(io, allocator, &.{"feature.txt"}); │ │674 + break :blk try client.commit(io, allocator, .{ .message = "revise feature" }); │ │675 + }; │ │676 + try client.push(io, allocator, "patch", "master:patch", false, .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } }); │ │677 + │ │678 + var second_revision_id: [evt.event_id_size]u8 = undefined; │ │679 + { │ │680 + var draft = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = draft_path }); │ │681 + defer draft.deinit(io, allocator); │ │682 + var arena = std.heap.ArenaAllocator.init(allocator); │ │683 + defer arena.deinit(); │ │684 + const moment = try evt.currentMoment(.{}, &draft); │ │685 + const patch = (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, moment, &arena, &fork_id)) orelse return error.NotFound; │ │686 + try std.testing.expect(patch.removed); │ │687 + const newest = (try evt.PatchRev.readNewest(evt.EventDB(.sha1), .sha1, moment, &arena)) orelse return error.NotFound; │ │688 + second_revision_id = newest.id; │ │689 + try std.testing.expect(!std.mem.eql(u8, &first_revision_id, &second_revision_id)); │ │690 + try std.testing.expectEqualStrings(&second_source_oid, newest.record.event.source_oid); │ │691 + try std.testing.expectEqualStrings("edited feature", newest.record.event.message); │ │692 + } │ │693 + { │ │694 + var target = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = target_path }); │ │695 + defer target.deinit(io, allocator); │ │696 + var arena = std.heap.ArenaAllocator.init(allocator); │ │697 + defer arena.deinit(); │ │698 + const moment = try evt.currentMoment(.{}, &target); │ │699 + const patch = (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, moment, &arena, &fork_id)) orelse return error.NotFound; │ │700 + try std.testing.expectEqualStrings("edited feature", patch.event.title); │ │701 + try std.testing.expectEqualStrings(&std.fmt.bytesToHex(second_revision_id, .lower), &(patch.event.patchrev_id orelse return error.NotFound));│ │702 + } │ │703 + │ │704 + // │ │705 + // reject extra refs without applying the patch update │ │706 + // │ │707 + │ │708 + const third_source_oid = blk: { │ │709 + const file = try client.core.work_dir.createFile(io, "feature.txt", .{ .truncate = true }); │ │710 + defer file.close(io); │ │711 + try file.writeStreamingAll(io, "rejected fork contents\n"); │ │712 + try client.add(io, allocator, &.{"feature.txt"}); │ │713 + break :blk try client.commit(io, allocator, .{ .message = "rejected revision" }); │ │714 + }; │ │715 + const extra_ref_rejected = if (client.push(io, allocator, "patch", "master:patch", false, .{ │ │716 + .refspecs = &.{"refs/heads/master:refs/heads/extra"}, │ │717 + .wire = .{ .ssh = .{ .command = ssh_cmd } }, │ │718 + })) |_| false else |_| true; │ │719 + try std.testing.expect(extra_ref_rejected); │ │720 + { │ │721 + var draft = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = draft_path }); │ │722 + defer draft.deinit(io, allocator); │ │723 + try std.testing.expectEqualStrings(&second_source_oid, &(try draft.readRef(io, fork.ref) orelse return error.NotFound)); │ │724 + try std.testing.expectEqual(null, try draft.readRef(io, .{ .kind = .head, .name = "extra" })); │ │725 + var arena = std.heap.ArenaAllocator.init(allocator); │ │726 + defer arena.deinit(); │ │727 + const moment = try evt.currentMoment(.{}, &draft); │ │728 + const newest = (try evt.PatchRev.readNewest(evt.EventDB(.sha1), .sha1, moment, &arena)) orelse return error.NotFound; │ │729 + try std.testing.expectEqualSlices(u8, &second_revision_id, &newest.id); │ │730 + } │ │731 + │ │732 + // │ │733 + // a removed target patch stays removed after another push │ │734 + // │ │735 + │ │736 + { │ │737 + var target = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = target_path }); │ │738 + defer target.deinit(io, allocator); │ │739 + try evt.remove(.xit, .{}, io, allocator, &target, &fork_id, .patch, .{ .name = "admin", .email = "admin@example.test" }); │ │740 + } │ │741 + try client.push(io, allocator, "patch", "master:patch", false, .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } }); │ │742 + { │ │743 + var target = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = target_path }); │ │744 + defer target.deinit(io, allocator); │ │745 + var arena = std.heap.ArenaAllocator.init(allocator); │ │746 + defer arena.deinit(); │ │747 + const moment = try evt.currentMoment(.{}, &target); │ │748 + const patch = (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, moment, &arena, &fork_id)) orelse return error.NotFound; │ │749 + try std.testing.expect(patch.removed); │ │750 } │ │751 │ │752 const other_url = try std.fmt.allocPrint(allocator, "git@localhost:admin/other/patch:{s}/branch:other", .{&fork_id_hex}); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │753 defer allocator.free(other_url); │ │754 try client.addRemote(io, allocator, .{ .name = "other", .value = other_url }); │ │618 - const wrong_repo_rejected = if (client.push(io, allocator, "other", fork.ref.name, false, .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } })) |_| false else |_| true; │ │755 + const wrong_repo_rejected = if (client.push(io, allocator, "other", "master:patch", false, .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } })) |_| false else |_| true;│ │756 try std.testing.expect(wrong_repo_rejected); │ │757 + │ │758 + // │ │759 + // the fork remains cloneable after the target repo is removed │ │760 + // │ │761 │ │762 { │ │763 var admin = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = admin_path }); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯