╭ changed ───────╮ │src/test/net.zig│ ╰────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 }); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │764 defer admin.deinit(io, allocator); │ │624 - try evt.consume(.xit, evt.admin_repo_opts, io, allocator, &admin, evt.events_ref, &.{.{ │ │625 - .id = std.fmt.bytesToHex(user_id, .lower), │ │626 - .timestamp = 2, │ │627 - .author = .{ .name = "admin", .email = "admin@example.test" }, │ │628 - .event = .{ .user = .{ │ │629 - .name = "admin", │ │630 - .email = "admin@example.test", │ │631 - .password_hash = "", │ │632 - .ssh_keys = "", │ │633 - } }, │ │634 - }}); │ │765 + try evt.remove(.xit, evt.admin_repo_opts, io, allocator, &admin, &repo_id, .repo, .{ .name = "admin", .email = "admin@example.test" });│ │766 + } │ │767 + const clone_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "fork-clone" }); │ │768 + defer allocator.free(clone_path); │ │769 + var fork_clone = try rp.Repo(.xit, .{ .is_test = true }).clone( │ │770 + io, │ │771 + allocator, │ │772 + remote_url, │ │773 + clone_path, │ │774 + clone_path, │ │775 + null, │ │776 + .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } }, │ │777 + ); │ │778 + defer fork_clone.deinit(io, allocator); │ │779 + try std.testing.expectEqualStrings(&third_source_oid, &(try fork_clone.readRef(io, fork.ref) orelse return error.NotFound)); │ │780 + │ │781 + { │ │782 + var admin = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = admin_path }); │ │783 + defer admin.deinit(io, allocator); │ │784 + try evt.consume(.xit, evt.admin_repo_opts, io, allocator, &admin, evt.events_ref, &.{ │ │785 + .{ │ │786 + .id = std.fmt.bytesToHex(repo_id, .lower), │ │787 + .timestamp = 2, │ │788 + .author = .{ .name = "admin", .email = "admin@example.test" }, │ │789 + .event = .{ .repo = .{ │ │790 + .user_id = &user_id, │ │791 + .name = "target", │ │792 + .description = "", │ │793 + } }, │ │794 + }, │ │795 + .{ │ │796 + .id = std.fmt.bytesToHex(user_id, .lower), │ │797 + .timestamp = 2, │ │798 + .author = .{ .name = "admin", .email = "admin@example.test" }, │ │799 + .event = .{ .user = .{ │ │800 + .name = "admin", │ │801 + .email = "admin@example.test", │ │802 + .password_hash = "", │ │803 + .ssh_keys = "", │ │804 + } }, │ │805 + }, │ │806 + }); │ │807 } │ │808 │ │809 const denied_url = try std.fmt.allocPrint(allocator, "git@localhost:admin/target/patch:{s}/branch:denied", .{&fork_id_hex}); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │810 defer allocator.free(denied_url); │ │811 try client.addRemote(io, allocator, .{ .name = "denied", .value = denied_url }); │ │640 - const unauthorized_rejected = if (client.push(io, allocator, "denied", fork.ref.name, false, .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } })) |_| false else |_| true; │ │812 + const unauthorized_rejected = if (client.push(io, allocator, "denied", "master:patch", false, .{ .wire = .{ .ssh = .{ .command = ssh_cmd } } })) |_| false else |_| true;│ │813 try std.testing.expect(unauthorized_rejected); │ │814 } │ │815 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯