╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────╮ │src/test/event.zig│ ╰──────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1297 } │ │1298 │ │1299 // delete repo1 -> dense order (no tombstone) │ │1300 - try evt.commitAndConsume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{ │ │1300 + try evt.consume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{ │ │1301 .{ .id = std.fmt.bytesToHex(repo_ids[1], .lower), .timestamp = 200, .event = .{ .repo = null } },│ │1302 }); │ │1303 { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1306 } │ │1307 │ │1308 // update repo0 at a later timestamp -> keeps its original place │ │1309 - try evt.commitAndConsume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{ │ │1309 + try evt.consume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{ │ │1310 .{ .id = std.fmt.bytesToHex(repo_ids[0], .lower), .timestamp = 300, .event = .{ .repo = .{ .user_id = &user_id, .name = "repo0", .description = "updated" } } },│ │1311 }); │ │1312 { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────╮ │src/test/net.zig│ ╰────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │37 } │ │38 } │ │39 │ │40 +test "push events" { │ │41 + const io = std.testing.io; │ │42 + const allocator = std.testing.allocator; │ │43 + if (.windows != builtin.os.tag) { │ │44 + try testPushEvents(.xit, .{ .wire = .ssh }, io, allocator);│ │45 + } │ │46 +} │ │47 + │ │48 test "clone small" { │ │49 const io = std.testing.io; │ │50 const allocator = std.testing.allocator; │ ╰───────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │592 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; │ │593 try std.testing.expectEqualStrings(&commit1, &oid_master); │ │594 } │ │595 + │ │596 +fn testPushEvents( │ │597 + comptime repo_kind: rp.RepoKind, │ │598 + comptime transport_def: net.TransportDefinition, │ │599 + io: std.Io, │ │600 + allocator: std.mem.Allocator, │ │601 +) !void { │ │602 + const temp_dir_name = "temp-testnet-push-events"; │ │603 │ │604 + // create the temp dir │ │605 + const cwd = std.Io.Dir.cwd(); │ │606 + var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); │ │607 + if (temp_dir_or_err) |*temp_dir| { │ │608 + temp_dir.close(io); │ │609 + try cwd.deleteTree(io, temp_dir_name); │ │610 + } else |_| {} │ │611 + var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); │ │612 + defer cwd.deleteTree(io, temp_dir_name) catch {}; │ │613 + defer temp_dir.close(io); │ │614 + │ │615 + // init server │ │616 + var server_process = try runServer(io, allocator, temp_dir_name); │ │617 + defer _ = server_process.kill(io); │ │618 + │ │619 + // register the repo under admin and locate its on-disk directory │ │620 + const server_path = (try repoOnDiskPath(io, allocator, temp_dir_name, "testrepo", true)).?; │ │621 + defer allocator.free(server_path); │ │622 + │ │623 + const repo_opts: rp.RepoOpts(.xit) = .{ .is_test = true }; │ │624 + const ServerRepo = rp.Repo(.xit, repo_opts); │ │625 + var server_repo = try ServerRepo.init(io, allocator, .{ .path = server_path }); │ │626 + defer server_repo.deinit(io, allocator); │ │627 + │ │628 + const cwd_path = try std.process.currentPathAlloc(io, allocator); │ │629 + defer allocator.free(cwd_path); │ │630 + │ │631 + const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" }); │ │632 + defer allocator.free(client_path); │ │633 + │ │634 + var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path }); │ │635 + defer client_repo.deinit(io, allocator); │ │636 + │ │637 + // add remote │ │638 + { │ │639 + const remote_url = try remoteUrl(transport_def, allocator, "testrepo"); │ │640 + defer allocator.free(remote_url); │ │641 + │ │642 + try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); │ │643 + } │ │644 + │ │645 + const is_ssh = switch (transport_def) { │ │646 + .file => false, │ │647 + .wire => |wire_kind| .ssh == wire_kind, │ │648 + }; │ │649 + const ssh_cmd_maybe = try sshCommand(is_ssh, allocator, cwd_path, temp_dir_name); │ │650 + defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); │ │651 + │ │652 + var arena = std.heap.ArenaAllocator.init(allocator); │ │653 + defer arena.deinit(); │ │654 + │ │655 + // │ │656 + // define test events │ │657 + // │ │658 + │ │659 + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); │ │660 + const issue_event_id = evt.EventWithId.randomId(prng.random()); │ │661 + │ │662 + const events_to_push = [_]evt.EventWithId{ │ │663 + .{ │ │664 + .id = std.fmt.bytesToHex(issue_event_id, .lower), │ │665 + .event = .{ │ │666 + .issue = .{ │ │667 + .title = "Login form clears password on validation error", │ │668 + .description = "Submitting an invalid email address resets the password field. Preserve the field value and show an inline validation message.", │ │669 + .tags = "bug priority-high ui", │ │670 + }, │ │671 + }, │ │672 + }, │ │673 + }; │ │674 + │ │675 + // commit the issue on the client and push it to the server │ │676 + try evt.consume(repo_kind, .{ .is_test = true }, io, allocator, &client_repo, evt.events_ref, &events_to_push); │ │677 + try client_repo.push( │ │678 + io, │ │679 + allocator, │ │680 + "origin", │ │681 + "haxy/events", │ │682 + false, │ │683 + .{ .wire = .{ .ssh = .{ │ │684 + .command = ssh_cmd_maybe, │ │685 + } } }, │ │686 + ); │ │687 + │ │688 + // the push consumed the issue into the server repo's db │ │689 + { │ │690 + const haxy_moment = try evt.currentMoment(repo_opts, &server_repo); │ │691 + │ │692 + // get the map of issues │ │693 + const event_id_to_issue_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->issue")) orelse return error.NotFound; │ │694 + const event_id_to_issue = try ServerRepo.DB.HashMap(.read_only).init(event_id_to_issue_cursor); │ │695 + │ │696 + // get the issue out of the map │ │697 + const issue_cursor = try event_id_to_issue.getCursor(hash.hashInt(repo_opts.hash, &issue_event_id)) orelse return error.NotFound; │ │698 + const issue_map = try ServerRepo.DB.HashMap(.read_only).init(issue_cursor); │ │699 + const issue = try evt.read(evt.Issue, ServerRepo.DB, repo_opts.hash, &arena, issue_map); │ │700 + │ │701 + try std.testing.expectEqualStrings(events_to_push[0].event.issue.?.description, issue.description); │ │702 + try std.testing.expectEqualStrings(events_to_push[0].event.issue.?.tags, issue.tags); │ │703 + } │ │704 + │ │705 + // │ │706 + // edit the issue │ │707 + // │ │708 + │ │709 + // this event edits the previous one because it has the same id │ │710 + const events_to_push2 = [_]evt.EventWithId{ │ │711 + .{ │ │712 + .id = std.fmt.bytesToHex(issue_event_id, .lower), │ │713 + .event = .{ │ │714 + .issue = .{ │ │715 + .title = "Login form clears password on validation error", │ │716 + .description = "Submitting an invalid email address resets the password field and removes typed input. Preserve the field value and show an inline validation message.",│ │717 + .tags = "bug priority-low ui", │ │718 + }, │ │719 + }, │ │720 + }, │ │721 + }; │ │722 + │ │723 + // commit the edit on the client and push it to the server │ │724 + try evt.consume(repo_kind, .{ .is_test = true }, io, allocator, &client_repo, evt.events_ref, &events_to_push2); │ │725 + try client_repo.push( │ │726 + io, │ │727 + allocator, │ │728 + "origin", │ │729 + "haxy/events", │ │730 + false, │ │731 + .{ .wire = .{ .ssh = .{ │ │732 + .command = ssh_cmd_maybe, │ │733 + } } }, │ │734 + ); │ │735 + │ │736 + // the push consumed the edit into the server repo's db │ │737 + { │ │738 + const haxy_moment = try evt.currentMoment(repo_opts, &server_repo); │ │739 + │ │740 + // get the map of issues │ │741 + const event_id_to_issue_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->issue")) orelse return error.NotFound; │ │742 + const event_id_to_issue = try ServerRepo.DB.HashMap(.read_only).init(event_id_to_issue_cursor); │ │743 + │ │744 + // get the issue out of the map that was edited │ │745 + const issue_cursor = try event_id_to_issue.getCursor(hash.hashInt(repo_opts.hash, &issue_event_id)) orelse return error.NotFound; │ │746 + const issue_map = try ServerRepo.DB.HashMap(.read_only).init(issue_cursor); │ │747 + const issue = try evt.read(evt.Issue, ServerRepo.DB, repo_opts.hash, &arena, issue_map); │ │748 + │ │749 + // the description and tags were correctly edited │ │750 + try std.testing.expectEqualStrings(events_to_push2[0].event.issue.?.description, issue.description); │ │751 + try std.testing.expectEqualStrings(events_to_push2[0].event.issue.?.tags, issue.tags); │ │752 + } │ │753 +} │ │754 + │ │755 fn testClone( │ │756 comptime repo_kind: rp.RepoKind, │ │757 comptime transport_def: net.TransportDefinition, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1404 var password_hash_buf: [evt.User.password_hash_max_len]u8 = undefined; │ │1405 const password_hash = try evt.User.hashPassword("password", &password_hash_buf, io); │ │1406 │ │1240 - try evt.commitAndConsume(.xit, evt.admin_repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{.{│ │1407 + try evt.consume(.xit, evt.admin_repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{.{ │ │1408 .id = std.fmt.bytesToHex(user_id, .lower), │ │1409 .event = .{ .user = .{ │ │1410 .name = "admin", │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │201 } │ │202 │ │203 // commit the seed events and consume them into the database │ │204 - try evt.commitAndConsume(.xit, evt.admin_repo_opts, io, allocator, &repo, evt.events_ref, &events_to_consume);│ │204 + try evt.consume(.xit, evt.admin_repo_opts, io, allocator, &repo, evt.events_ref, &events_to_consume); │ │205 │ │206 // every repo gets the same generated history, so build it once into a │ │207 // template repo and copy that to each repo's location below rather than │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │527 }, │ │528 }; │ │529 } │ │530 - try evt.commitAndConsume(.xit, .{}, io, allocator, &template_repo, evt.events_ref, &issue_events);│ │530 + try evt.consume(.xit, .{}, io, allocator, &template_repo, evt.events_ref, &issue_events); │ │531 } │ │532 │ │533 // copy the template to each repo's on-disk location, named by its │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │173 switch (any_repo) { │ │174 inline else => |*opened| { │ │175 // local mode: bring the event db up to date with the events branch │ │176 - if (session.local != null) try evt.consume(repo_kind, opened.self_repo_opts, io, gpa, opened, evt.events_ref); │ │176 + if (session.local != null) try evt.consume(repo_kind, opened.self_repo_opts, io, gpa, opened, evt.events_ref, &.{}); │ │177 break :blk .{ │ │178 try Files.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, repo_identity.identity, requested_ref_or_oid, requested_ref_value, files_dir, files_line),│ │179 try Commits.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, repo_identity.identity, requested_ref_or_oid, requested_ref_value, commits_content), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1108 var any_repo = try rp.AnyRepo(repo_kind, .{}).open(io, allocator, .{ .path = src.path }); │ │1109 defer any_repo.deinit(io, allocator); │ │1110 switch (any_repo) { │ │1111 - inline else => |*repo| try evt.commitAndConsume(repo_kind, repo.self_repo_opts, io, allocator, repo, evt.events_ref, &.{event}),│ │1111 + inline else => |*repo| try evt.consume(repo_kind, repo.self_repo_opts, io, allocator, repo, evt.events_ref, &.{event}), │ │1112 } │ │1113 }, │ │1114 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │423 │ │424 var repo = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = repo_path }); │ │425 defer repo.deinit(io, allocator); │ │426 - try evt.commitAndConsume(.xit, .{}, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{event});│ │426 + try evt.consume(.xit, .{}, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{event}); │ │427 }, │ │428 .local => |src| { │ │429 // the local form posts to "/issue", so base is empty │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯