╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 95 } │ │ 96 }; │ │ 97 │ │ 98 +// consume the events on `ref` into the db the repo's views read: the repo's │ │ 99 +// own db for a xit repo, or the standalone event db next to a git repo │ │100 pub fn consume( │ │ 99 - comptime repo_opts: rp.RepoOpts(.xit), │ │101 + comptime repo_kind: rp.RepoKind, │ │102 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │103 io: std.Io, │ │104 allocator: std.mem.Allocator, │ │102 - repo: *rp.Repo(.xit, repo_opts), │ │105 + repo: *rp.Repo(repo_kind, repo_opts), │ │106 ref: rf.Ref, │ │107 ) !void { │ │105 - const DB = rp.Repo(.xit, repo_opts).DB; │ │106 - const State = rp.Repo(.xit, repo_opts).State; │ │107 - │ │108 - const Ctx = struct { │ │109 - core: *rp.Repo(.xit, repo_opts).Core, │ │110 - io: std.Io, │ │111 - allocator: std.mem.Allocator, │ │112 - ref: rf.Ref, │ │113 - │ │114 - pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │115 - var moment = try DB.HashMap(.read_write).init(cursor.*); │ │116 - const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │117 - try consumeInTransaction(.xit, repo_opts, state.readOnly(), &ctx.core.db, &moment, ctx.io, ctx.allocator, ctx.ref); │ │108 + // a branch that only exists on a remote is consumed from its remote-tracking ref │ │109 + const resolved_ref: rf.Ref, const tip: [hash.hexLen(repo_opts.hash)]u8 = blk: { │ │110 + if (try repo.readRef(io, ref)) |oid| break :blk .{ ref, oid }; │ │111 + var remotes = try repo.listRemotes(io, allocator); │ │112 + defer remotes.deinit(); │ │113 + for (remotes.sections.keys()) |remote_name| { │ │114 + const remote_ref: rf.Ref = .{ .kind = .{ .remote = remote_name }, .name = ref.name }; │ │115 + if (try repo.readRef(io, remote_ref)) |oid| break :blk .{ remote_ref, oid }; │ │116 } │ │117 + // the branch is gone (or never existed): drop any db from a previous │ │118 + // sync rather than serving its issues indefinitely │ │119 + if (repo_kind == .git) try LocalEventDB(repo_opts.hash).delete(io, repo.core.repo_dir); │ │120 + return; │ │121 }; │ │122 + var tip_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │123 + _ = try std.fmt.hexToBytes(&tip_bytes, &tip); │ │124 │ │121 - try repo.core.db_file.lock(io, .exclusive); │ │122 - defer repo.core.db_file.unlock(io); │ │125 + switch (repo_kind) { │ │126 + .git => { │ │127 + var event_db = try LocalEventDB(repo_opts.hash).open(io, allocator, repo.core.repo_dir); │ │128 + defer event_db.deinit(io, allocator); │ │129 │ │124 - const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │125 - try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │126 - .core = &repo.core, │ │127 - .io = io, │ │128 - .allocator = allocator, │ │129 - .ref = ref, │ │130 - }); │ │130 + if (try lastConsumedOid(repo_opts.hash, event_db.db)) |last_oid| { │ │131 + if (std.mem.eql(u8, &last_oid, &tip_bytes)) return; │ │132 + } │ │133 + │ │134 + try event_db.consume(repo_kind, repo_opts, io, allocator, repo, resolved_ref); │ │135 + }, │ │136 + .xit => { │ │137 + const DB = rp.Repo(.xit, repo_opts).DB; │ │138 + const State = rp.Repo(.xit, repo_opts).State; │ │139 + │ │140 + const Ctx = struct { │ │141 + core: *rp.Repo(.xit, repo_opts).Core, │ │142 + io: std.Io, │ │143 + allocator: std.mem.Allocator, │ │144 + ref: rf.Ref, │ │145 + │ │146 + pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │147 + var moment = try DB.HashMap(.read_write).init(cursor.*); │ │148 + const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │149 + try consumeInTransaction(.xit, repo_opts, state.readOnly(), &ctx.core.db, &moment, ctx.io, ctx.allocator, ctx.ref);│ │150 + } │ │151 + }; │ │152 + │ │153 + try repo.core.db_file.lock(io, .exclusive); │ │154 + defer repo.core.db_file.unlock(io); │ │155 + │ │156 + if (try lastConsumedOid(repo_opts.hash, &repo.core.db)) |last_oid| { │ │157 + if (std.mem.eql(u8, &last_oid, &tip_bytes)) return; │ │158 + } │ │159 + │ │160 + const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │161 + try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │162 + .core = &repo.core, │ │163 + .io = io, │ │164 + .allocator = allocator, │ │165 + .ref = resolved_ref, │ │166 + }); │ │167 + }, │ │168 + } │ │169 } │ │170 │ │171 // a standalone event db holding events consumed from a local repo's events │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │180 const db_name = "haxy"; │ │181 const Self = @This(); │ │182 │ │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 { │ │183 + // open the db in `repo_dir` for writing, creating it if it doesn't exist │ │184 + pub fn open(io: std.Io, allocator: std.mem.Allocator, repo_dir: std.Io.Dir) !Self { │ │185 const file = try repo_dir.createFile(io, db_name, .{ .truncate = false, .read = true, .lock = .exclusive });│ │186 return fromFile(io, allocator, file); │ │187 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │188 │ │189 // 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 { │ │190 + pub fn openReadOnly(io: std.Io, allocator: std.mem.Allocator, repo_dir: std.Io.Dir) !?Self { │ │191 const file = repo_dir.openFile(io, db_name, .{ .mode = .read_write, .lock = .shared }) catch |err| switch (err) {│ │192 error.FileNotFound => return null, │ │193 else => |e| return e, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │227 } │ │228 │ │229 // 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 │ │230 + // from `open`, whose handle holds the exclusive lock guarding the │ │231 // transaction. │ │232 pub fn consume( │ │233 self: *Self, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │264 .allocator = allocator, │ │265 .ref = ref, │ │266 }); │ │229 - } │ │230 - }; │ │231 -} │ │232 - │ │233 -// bring a local repo's event db up to date with its events branch │ │234 -pub fn syncLocalEvents( │ │235 - comptime repo_kind: rp.RepoKind, │ │236 - comptime repo_opts: rp.RepoOpts(repo_kind), │ │237 - io: std.Io, │ │238 - allocator: std.mem.Allocator, │ │239 - repo: *rp.Repo(repo_kind, repo_opts), │ │240 -) !void { │ │241 - var remotes = try repo.listRemotes(io, allocator); │ │242 - defer remotes.deinit(); │ │243 - │ │244 - const ref: rf.Ref, const tip: [hash.hexLen(repo_opts.hash)]u8 = blk: { │ │245 - if (try repo.readRef(io, events_ref)) |oid| break :blk .{ events_ref, oid }; │ │246 - for (remotes.sections.keys()) |remote_name| { │ │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 }; │ │267 } │ │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; │ │268 }; │ │255 - │ │256 - var event_db = try LocalEventDB(repo_opts.hash).create(io, allocator, repo.core.repo_dir); │ │257 - defer event_db.deinit(io, allocator); │ │258 - │ │259 - if (try lastConsumedOid(repo_opts.hash, event_db.db)) |last_oid| { │ │260 - var tip_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │261 - _ = try std.fmt.hexToBytes(&tip_bytes, &tip); │ │262 - if (std.mem.eql(u8, &last_oid, &tip_bytes)) return; │ │263 - } │ │264 - │ │265 - try event_db.consume(repo_kind, repo_opts, io, allocator, repo, ref); │ │269 } │ │270 │ │271 // consume the events on `ref` into the db `moment` writes to. events are read │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │522 return try DB.HashMap(.read_only).init(haxy_moment_cursor); │ │523 } │ │524 │ │522 -// commit each event as a JSON commit message on `ref`, then consume them into│ │523 -// the db the repo's views read: the repo's own db, or the standalone local │ │524 -// event db when `is_local`. │ │525 +// commit each event as a JSON commit message on `ref`, then consume them │ │526 pub fn commitAndConsume( │ │527 comptime repo_kind: rp.RepoKind, │ │528 comptime repo_opts: rp.RepoOpts(repo_kind), │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │531 repo: *rp.Repo(repo_kind, repo_opts), │ │532 ref: rf.Ref, │ │533 events: []const EventWithId, │ │533 - is_local: bool, │ │534 ) !void { │ │535 // a branch that only exists on a remote continues from the remote tip, │ │536 // so a cloned repo's local branch stays connected to the server history│ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │557 parent_oids = null; │ │558 } │ │559 │ │560 - if (is_local) { │ │561 - try syncLocalEvents(repo_kind, repo_opts, io, allocator, repo); │ │562 - } else switch (repo_kind) { │ │563 - .xit => try consume(repo_opts, io, allocator, repo, ref), │ │564 - // server repos are always xit │ │565 - .git => unreachable, │ │566 - } │ │560 + try consume(repo_kind, repo_opts, io, allocator, repo, ref); │ │561 } │ │562 │ │563 // build the key for SortedSets sorted by timestamp. the big-endian timestamp│ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────╮ │636 .description = "", │ │637 .enable_issue = true,│ │638 } }, │ │645 - }}, false); │ │639 + }}); │ │640 │ │641 return event_id_hex; │ │642 } │ ╰──────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │247 .id = std.fmt.bytesToHex(user_id[0..evt.event_id_size].*, .lower), │ │248 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()),│ │249 .event = .{ .user = updated }, │ │250 - }}, false); │ │250 + }}); │ │251 } │ │252 │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯