╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │424 defer allocator.free(root); │ │425 const repos_dir = try std.fs.path.join(allocator, &.{ root, "repos" }); │ │426 defer allocator.free(repos_dir); │ │427 + const admin_path = try std.fs.path.join(allocator, &.{ root, "admin" }); │ │428 + defer allocator.free(admin_path); │ │429 const upstream_path = try std.fs.path.join(allocator, &.{ root, "upstream" }); │ │430 defer allocator.free(upstream_path); │ │429 - const target_path = try std.fs.path.join(allocator, &.{ repos_dir, "target" }); │ │431 + │ │432 + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); │ │433 + const user_id = evt.EventWithId.randomId(prng.random()); │ │434 + const repo_id = evt.EventWithId.randomId(prng.random()); │ │435 + const repo_id_hex = std.fmt.bytesToHex(repo_id, .lower); │ │436 + const target_path = try std.fs.path.join(allocator, &.{ repos_dir, &repo_id_hex }); │ │437 defer allocator.free(target_path); │ │438 │ │439 + var admin = try rp.Repo(.xit, evt.admin_repo_opts).init(io, allocator, .{ .path = admin_path });│ │440 + defer admin.deinit(io, allocator); │ │441 + try evt.consume(.xit, evt.admin_repo_opts, io, allocator, &admin, evt.events_ref, &.{ │ │442 + .{ │ │443 + .id = std.fmt.bytesToHex(user_id, .lower), │ │444 + .timestamp = 1, │ │445 + .author = author, │ │446 + .event = .{ .user = .{ │ │447 + .name = author.name, │ │448 + .email = author.email, │ │449 + .password_hash = "", │ │450 + } }, │ │451 + }, │ │452 + .{ │ │453 + .id = repo_id_hex, │ │454 + .timestamp = 1, │ │455 + .author = author, │ │456 + .event = .{ .repo = .{ │ │457 + .user_id = &user_id, │ │458 + .name = "repo", │ │459 + .description = "", │ │460 + } }, │ │461 + }, │ │462 + }); │ │463 + │ │464 // │ │465 // create the target repo │ │466 // │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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" }); │ │448 - │ │449 - var prng = std.Random.DefaultPrng.init(std.testing.random_seed); │ │450 - const unused_id = evt.EventWithId.randomId(prng.random()); │ │451 - const unused_hex = std.fmt.bytesToHex(unused_id, .lower); │ │452 - │ │453 - const route_text = try std.fmt.allocPrint(allocator, "alice/repo/patch:{s}/branch:feature/topic", .{&unused_hex});│ │454 - defer allocator.free(route_text); │ │455 - const route = fork.parseRoute(route_text) orelse return error.InvalidRoute; │ │456 - try std.testing.expectEqualStrings("alice/repo", route.identity); │ │457 - try std.testing.expectEqualStrings("feature/topic", route.target); │ │458 - │ │459 - // │ │460 - // reserve an author-bound draft │ │461 - // │ │462 - │ │463 - const unused_path = try fork.create(repo_opts, io, allocator, repos_dir, &unused_hex, author.name); │ │464 - defer allocator.free(unused_path); │ │465 - var unused = try Repo.open(io, allocator, .{ .path = unused_path }); │ │466 - defer unused.deinit(io, allocator); │ │467 - const stored_author = (try fork.draftAuthor(repo_opts, &unused, io, allocator)) orelse return error.NotFound; │ │468 - defer allocator.free(stored_author); │ │469 - try std.testing.expectEqualStrings(author.name, stored_author); │ │470 - var head_buf: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; │ │471 - const head = try unused.head(io, &head_buf); │ │472 - switch (head) { │ │473 - .ref => |head_ref| try std.testing.expectEqualStrings(fork.ref.name, head_ref.name), │ │474 - .oid => return error.InvalidRef, │ │475 - } │ │480 │ │481 // │ │478 - // push a proposed commit into another draft │ │482 + // push a proposed commit into a draft │ │483 // │ │484 │ │485 const patch_id = evt.EventWithId.randomId(prng.random()); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │486 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, &.{.{ │ │490 + .id = patch_id_hex, │ │491 + .timestamp = 2, │ │492 + .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; │ │502 { │ │503 defer draft.deinit(io, allocator); │ │489 - try draft.addConfig(io, allocator, .{ .name = "haxy.author", .value = author.name }); │ │490 - try draft.addConfig(io, allocator, .{ .name = "haxy.target", .value = "master" }); │ │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"}); │ │495 - _ = try draft.commit(io, allocator, .{ .author = "alice <alice@example.test>", .message = "add feature", .timestamp = 2 }); │ │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 }); │ │510 } │ │511 + try fork.recordPush(io, allocator, &admin, &patch_id_hex, "master", &source_oid, author, 3); │ │512 │ │513 // │ │514 // publish the staged code and metadata │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │515 // │ │516 │ │503 - try fork.publish(repo_opts, io, allocator, &target, draft_path, .{ │ │517 + try fork.publish(repo_opts, io, allocator, &admin, &target, draft_path, .{│ │518 .id = patch_id_hex, │ │519 + .user_id = user_id, │ │520 + .repo_id = repo_id, │ │521 .title = "add the answer", │ │522 .tags = "enhancement", │ │523 .description = "adds a reusable answer constant", │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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; │ │537 │ │538 + var fork_arena = std.heap.ArenaAllocator.init(allocator); │ │539 + defer fork_arena.deinit(); │ │540 + 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;│ │542 + try std.testing.expect(!published_fork.removed); │ │543 + │ │544 // │ │545 // edit metadata without replacing the code revision │ │546 // │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │547 │ │525 - try fork.publish(repo_opts, io, allocator, &target, draft_path, .{ │ │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", │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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;│ │536 - try std.testing.expectEqualStrings(&patch.event.patchrev_id, &edited.event.patchrev_id); │ │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 │ │542 - var reopened = try Repo.open(io, allocator, .{ .path = draft_path }); │ │543 - defer reopened.deinit(io, allocator); │ │544 - try fork.setTarget(repo_opts, &reopened, io, allocator, "next"); │ │545 - try fork.publish(repo_opts, io, allocator, &target, draft_path, .{ │ │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, │ │547 - .title = edited.event.title, │ │548 - .tags = edited.event.tags, │ │549 - .description = edited.event.description, │ │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 }); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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); │ │557 - try std.testing.expect(!std.mem.eql(u8, &edited.event.patchrev_id, &retargeted.event.patchrev_id)); │ │583 + try std.testing.expect(!std.mem.eql(u8, &edited_patchrev_hex, &retargeted.event.patchrev_id)); │ │584 } │ │585 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯