╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────╮ │556 │ │557 // the key holder a push acts as │ │558 const Pusher = struct { │ │559 + user_id: [evt.event_id_size]u8, │ │560 author: evt.CommitAuthor, │ │561 protected_branches: []const []const u8,│ │562 locked_branches: []const []const u8, │ ╰────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │620 // members change protected branches only by merging a patch│ │621 const owner = holder.role == .owner; │ │622 break :blk .{ .push = .{ │ │623 + .user_id = holder.id, │ │624 .author = try commitAuthor(moment, arena, &holder.id), │ │625 .protected_branches = if (owner) names else &.{}, │ │626 .locked_branches = if (owner) &.{} else names, │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────────────╮ │src/test/event/notify.zig│ ╰─────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │26 return try evt.Notify.readMode(evt.UserDB, evt.user_repo_opts.hash, moment, arena, repo_id, thread_id); │ │27 } │ │28 │ │29 -fn readSubscribers(io: std.Io, allocator: std.mem.Allocator, arena: *std.heap.ArenaAllocator, users_dir: []const u8, owner_id: *const [evt.event_id_size]u8, notif_id: *const [evt.event_id_size]u8) ![]const [evt.event_id_size]u8 {│ │29 +fn readNotified(io: std.Io, allocator: std.mem.Allocator, arena: *std.heap.ArenaAllocator, users_dir: []const u8, owner_id: *const [evt.event_id_size]u8, notif_id: *const [evt.event_id_size]u8) ![]const [evt.event_id_size]u8 { │ │30 var user_repo = (try evt.openUserRepo(io, allocator, users_dir, owner_id)) orelse return error.TestUnexpectedResult; │ │31 defer user_repo.deinit(io, allocator); │ │32 - return try notify.readSubscribers(try user_repo.core.latestMoment(), arena, notif_id); │ │32 + return try notify.readNotified(try user_repo.core.latestMoment(), arena, notif_id); │ │33 +} │ │34 + │ │35 +fn initUserRepo(io: std.Io, allocator: std.mem.Allocator, users_dir: []const u8, user_id: *const [evt.event_id_size]u8) !void { │ │36 + const path = try evt.userRepoPath(allocator, users_dir, user_id); │ │37 + defer allocator.free(path); │ │38 + var user_repo = try evt.initUserRepo(io, allocator, path); │ │39 + defer user_repo.deinit(io, allocator); │ │40 } │ │41 │ │42 test "notifs" { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │56 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); │ │57 const owner_id = evt.EventWithId.randomId(prng.random()); │ │58 const recipient_id = evt.EventWithId.randomId(prng.random()); │ │52 - for ([_]*const [evt.event_id_size]u8{ &owner_id, &recipient_id }) |user_id| { │ │53 - const path = try evt.userRepoPath(allocator, users_dir, user_id); │ │54 - defer allocator.free(path); │ │55 - var user_repo = try evt.initUserRepo(io, allocator, path); │ │56 - defer user_repo.deinit(io, allocator); │ │57 - } │ │59 + for ([_]*const [evt.event_id_size]u8{ &owner_id, &recipient_id }) |user_id| try initUserRepo(io, allocator, users_dir, user_id);│ │60 const location = try evt.createRepo(io, allocator, users_dir, &owner_id, author, "repo", "", .private, repo_opts.hash); │ │61 │ │62 // │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │126 │ │127 // subscribing replaces the mute record and indexes the subscriber in the owner's repo │ │128 const id_b = notify.idOf(&location.repo_id, &thread_b); │ │127 - try std.testing.expectEqual(.mute, try readMode(io, allocator, &arena, users_dir, &recipient_id, &location.repo_id, &thread_b)); │ │129 + try std.testing.expectEqual(.muted, try readMode(io, allocator, &arena, users_dir, &recipient_id, &location.repo_id, &thread_b)); │ │130 try notify.subscribe(io, allocator, users_dir, &recipient_id, location, &thread_b, author); │ │129 - try std.testing.expectEqual(.subscribe, try readMode(io, allocator, &arena, users_dir, &recipient_id, &location.repo_id, &thread_b)); │ │131 + try std.testing.expectEqual(.subscribed, try readMode(io, allocator, &arena, users_dir, &recipient_id, &location.repo_id, &thread_b));│ │132 try notify.deliver(io, allocator, users_dir, &recipient_id, &.{notif_b}); │ │133 try std.testing.expectEqual(1, (try readRecent(io, allocator, &arena, users_dir, &recipient_id)).len); │ │134 { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │133 - const subscribers = try readSubscribers(io, allocator, &arena, users_dir, &owner_id, &id_b);│ │134 - try std.testing.expectEqual(1, subscribers.len); │ │135 - try std.testing.expectEqualSlices(u8, &recipient_id, &subscribers[0]); │ │135 + const notified = try readNotified(io, allocator, &arena, users_dir, &owner_id, &id_b); │ │136 + try std.testing.expectEqual(1, notified.len); │ │137 + try std.testing.expectEqualSlices(u8, &recipient_id, &notified[0]); │ │138 } │ │139 │ │140 // muting drops the subscriber from the index │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │141 try notify.mute(io, allocator, users_dir, &recipient_id, location, &thread_b, author); │ │140 - try std.testing.expectEqual(0, (try readSubscribers(io, allocator, &arena, users_dir, &owner_id, &id_b)).len); │ │142 + try std.testing.expectEqual(0, (try readNotified(io, allocator, &arena, users_dir, &owner_id, &id_b)).len); │ │143 + │ │144 + // a full subscribed tier refuses one more │ │145 + for (0..notify.max_subscribed) |_| { │ │146 + const user_id = evt.EventWithId.randomId(prng.random()); │ │147 + try initUserRepo(io, allocator, users_dir, &user_id); │ │148 + try notify.subscribe(io, allocator, users_dir, &user_id, location, &thread_a, author); │ │149 + } │ │150 + try notify.subscribe(io, allocator, users_dir, &recipient_id, location, &thread_a, author); │ │151 + try std.testing.expectEqual(null, try readMode(io, allocator, &arena, users_dir, &recipient_id, &location.repo_id, &thread_a));│ │152 │ │153 // past the cap, the oldest go. times tie within a second, so which one goes isn't checked. │ │154 { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │942 } │ │943 } │ │944 │ │945 - // seeding consumed locally, which subscribes nobody, so subscribe admin as the server │ │946 - // would have, then deliver the notifications of its own repos the way the worker does │ │945 + // seeding consumed locally, which notifies nobody, so involve admin in its threads as the │ │946 + // server would have, then deliver each transaction of its own repos │ │947 for (repo_data, repo_event_ids, 0..) |r, id_bytes, repo_index| { │ │948 if (r.user_index != 0) continue; │ │949 const location: evt.RepoLocation = .{ .owner_id = user_ids[r.user_index], .repo_id = id_bytes };│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │950 // only the last repo has patches │ │951 const thread_ids: []const [evt.event_id_size]u8 = if (repo_index + 1 == repo_data.len) &admin_thread_ids else admin_thread_ids[0..2]; │ │952 - for (thread_ids) |*thread_id| try notify.subscribe(io, allocator, users_dir, &admin_user_id, location, thread_id, .{ .name = user_data[0].name, .email = user_data[0].email }); │ │953 - try notify.notifyRepo(io, allocator, users_dir, work_path, location); │ │952 + for (thread_ids) |*thread_id| try notify.writeMode(io, allocator, users_dir, &admin_user_id, &location, thread_id, .involved, .{ .name = user_data[0].name, .email = user_data[0].email });│ │953 + │ │954 + const repo_path = try evt.repoPath(arena.allocator(), users_dir, &location.owner_id, &location.repo_id); │ │955 + var target_repo = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = repo_path, .require_repo_root = true }); │ │956 + defer target_repo.deinit(io, allocator); │ │957 + const index_cursor = try (try evt.currentMoment(.{}, &target_repo)).getCursor(hash.hashInt(.sha1, evt.moment_index_key)) orelse return error.CursorNotFound; │ │958 + const current = try index_cursor.readUint(); │ │959 + for (0..current + 1) |moment_index| { │ │960 + notify.deliverTransaction(io, allocator, .{ .users_dir = users_dir, .admin_repo_path = work_path }, .{}, &target_repo, moment_index, null, null); │ │961 + } │ │962 } │ │963 │ │964 break :blk try ui.Session.init(&session_arena, &repo, .{ .user_id = &admin_user_id }); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────╮ │18 const not_found: []const u8 = ""; │ │19 } else @import("ansi_art"); │ │20 const evt = @import("./event.zig"); │ │21 -const notify = @import("./notify.zig"); │ │21 const inp = @import("./ui/input.zig"); │ │22 │ │23 pub const timestamp = @import("./ui/timestamp.zig");│ ╰────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯