╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │144 │ │145 // open the db in `repo_dir`, creating it if it doesn't exist │ │146 pub fn create(io: std.Io, allocator: std.mem.Allocator, repo_dir: std.Io.Dir) !Self { │ │147 - const file = try repo_dir.createFile(io, db_name, .{ .truncate = false, .read = true }); │ │147 + const file = try repo_dir.createFile(io, db_name, .{ .truncate = false, .read = true, .lock = .exclusive });│ │148 return fromFile(io, allocator, file); │ │149 } │ │150 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │151 - // open the db in `repo_dir`, or null if it doesn't exist │ │151 + // open the db in `repo_dir` for reading, or null if it doesn't exist │ │152 pub fn open(io: std.Io, allocator: std.mem.Allocator, repo_dir: std.Io.Dir) !?Self { │ │153 - const file = repo_dir.openFile(io, db_name, .{ .mode = .read_write, .lock = .none }) catch |err| switch (err) { │ │153 + const file = repo_dir.openFile(io, db_name, .{ .mode = .read_write, .lock = .shared }) catch |err| switch (err) {│ │154 error.FileNotFound => return null, │ │155 else => |e| return e, │ │156 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │157 return try fromFile(io, allocator, file); │ │158 } │ │159 │ │160 + // remove the db from `repo_dir`, so a repo whose events branch │ │161 + // disappeared doesn't keep showing stale issues │ │162 + pub fn delete(io: std.Io, repo_dir: std.Io.Dir) !void { │ │163 + repo_dir.deleteFile(io, db_name) catch |err| switch (err) { │ │164 + error.FileNotFound => {}, │ │165 + else => |e| return e, │ │166 + }; │ │167 + } │ │168 + │ │169 fn fromFile(io: std.Io, allocator: std.mem.Allocator, file: std.Io.File) !Self {│ │170 errdefer file.close(io); │ │171 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │188 allocator.destroy(self.db); │ │189 } │ │190 │ │182 - // consume the events on `repo`'s `ref` into this db │ │191 + // consume the events on `repo`'s `ref` into this db. the db must come│ │192 + // from `create`, whose handle holds the exclusive lock guarding the │ │193 + // transaction. │ │194 pub fn consume( │ │195 self: *Self, │ │196 comptime repo_kind: rp.RepoKind, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │217 try consumeInTransaction(repo_kind, repo_opts, read_state, ctx.db, &moment, ctx.io, ctx.allocator, ctx.ref);│ │218 } │ │219 }; │ │209 - │ │210 - try self.file.lock(io, .exclusive); │ │211 - defer self.file.unlock(io); │ │220 │ │221 const history = try DB.ArrayList(.read_write).init(self.db.rootCursor()); │ │222 try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │247 const remote_ref: rf.Ref = .{ .kind = .{ .remote = remote_name }, .name = events_ref.name };│ │248 if (try repo.readRef(io, remote_ref)) |oid| break :blk .{ remote_ref, oid }; │ │249 } │ │250 + // the branch is gone (or never existed): drop any db from a previous │ │251 + // sync rather than serving its issues indefinitely │ │252 + try LocalEventDB(repo_opts.hash).delete(io, repo.core.repo_dir); │ │253 return; │ │254 }; │ │255 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │172 switch (any_repo) { │ │173 inline else => |*opened| { │ │174 // local mode: bring the event db up to date with the events branch │ │175 - if (session.local != null) evt.syncLocalEvents(repo_kind, opened.self_repo_opts, io, gpa, opened) catch {}; │ │175 + const is_local = session.local != null; │ │176 + if (is_local) try evt.syncLocalEvents(repo_kind, opened.self_repo_opts, io, gpa, opened); │ │177 break :blk .{ │ │178 try Files.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, rf.identity, requested_ref_or_oid, requested_ref_value, files_dir, files_after),│ │179 try Commits.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, rf.identity, requested_ref_or_oid, requested_ref_value, commits_after), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │180 try Refs.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, rf.identity, refs_kind, refs_after), │ │180 - try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, rf.identity, issues_tag, issues_selected), │ │181 + try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, is_local, rf.identity, issues_tag, issues_selected),│ │182 }; │ │183 }, │ │184 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │52 │ │53 // read one window of an opened repo's issues (filtered to `tag` when set), │ │54 // ordered by creation time (oldest first), starting at the issue │ │55 -// `selected_id` names ("" = the beginning). issues live in the repo's own │ │56 -// xit db, so a git-backed repo has none. │ │55 +// `selected_id` names ("" = the beginning). a local session reads the event│ │56 +// db next to the repo (synced from the events branch on each page build); │ │57 +// a server repo reads its own db. │ │58 pub fn init( │ │59 comptime repo_kind: rp.RepoKind, │ │60 comptime repo_opts: rp.RepoOpts(repo_kind), │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────╮ │61 arena: *std.heap.ArenaAllocator, │ │62 repo: *rp.Repo(repo_kind, repo_opts),│ │63 io: std.Io, │ │64 + is_local: bool, │ │65 identity: []const u8, │ │66 tag: []const u8, │ │67 selected_id: []const u8, │ ╰─────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯