╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │21 // which may use different options │ │22 pub const admin_repo_opts: rp.RepoOpts(.xit) = .{}; │ │23 pub const AdminDB = rp.Repo(.xit, admin_repo_opts).DB; │ │24 + │ │25 +// the xitdb type events are stored in. it only depends on the hash kind, so│ │26 +// this matches the db of any xit repo using that hash kind. │ │27 +pub fn EventDB(comptime hash_kind: hash.HashKind) type { │ │28 + return rp.Repo(.xit, .{ .hash = hash_kind }).DB; │ │29 +} │ │30 │ │31 pub const EventKind = enum { │ │32 user, │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 } }; │ │111 - try consumeInTransaction(repo_opts, state, ctx.io, ctx.allocator, ctx.ref); │ │117 + try consumeInTransaction(.xit, repo_opts, state.readOnly(), &ctx.core.db, &moment, ctx.io, ctx.allocator, ctx.ref);│ │118 } │ │119 }; │ │120 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │128 .allocator = allocator, │ │129 .ref = ref, │ │130 }); │ │131 +} │ │132 + │ │133 +// a standalone event db holding events consumed from a local repo's events │ │134 +// branch. it lives inside the repo dir but is not part of the repo's own data, │ │135 +// so viewing a repo never mutates it. │ │136 +pub fn LocalEventDB(comptime hash_kind: hash.HashKind) type { │ │137 + return struct { │ │138 + file: std.Io.File, │ │139 + buffer: *std.Io.Writer.Allocating, │ │140 + db: *EventDB(hash_kind), │ │141 + │ │142 + const db_name = "haxy"; │ │143 + const Self = @This(); │ │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 }); │ │148 + return fromFile(io, allocator, file); │ │149 + } │ │150 + │ │151 + // open the db in `repo_dir`, 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) { │ │154 + error.FileNotFound => return null, │ │155 + else => |e| return e, │ │156 + }; │ │157 + return try fromFile(io, allocator, file); │ │158 + } │ │159 + │ │160 + fn fromFile(io: std.Io, allocator: std.mem.Allocator, file: std.Io.File) !Self { │ │161 + errdefer file.close(io); │ │162 + │ │163 + const buffer = try allocator.create(std.Io.Writer.Allocating); │ │164 + errdefer allocator.destroy(buffer); │ │165 + buffer.* = std.Io.Writer.Allocating.init(allocator); │ │166 + errdefer buffer.deinit(); │ │167 + │ │168 + const db = try allocator.create(EventDB(hash_kind)); │ │169 + errdefer allocator.destroy(db); │ │170 + db.* = try EventDB(hash_kind).init(.{ .io = io, .file = file, .buffer = buffer }); │ │171 + │ │172 + return .{ .file = file, .buffer = buffer, .db = db }; │ │173 + } │ │174 + │ │175 + pub fn deinit(self: *Self, io: std.Io, allocator: std.mem.Allocator) void { │ │176 + self.file.close(io); │ │177 + self.buffer.deinit(); │ │178 + allocator.destroy(self.buffer); │ │179 + allocator.destroy(self.db); │ │180 + } │ │181 + │ │182 + // consume the events on `repo`'s `ref` into this db │ │183 + pub fn consume( │ │184 + self: *Self, │ │185 + comptime repo_kind: rp.RepoKind, │ │186 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │187 + io: std.Io, │ │188 + allocator: std.mem.Allocator, │ │189 + repo: *rp.Repo(repo_kind, repo_opts), │ │190 + ref: rf.Ref, │ │191 + ) !void { │ │192 + comptime if (repo_opts.hash != hash_kind) @compileError("the repo must use the db's hash kind"); │ │193 + const DB = EventDB(hash_kind); │ │194 + │ │195 + const Ctx = struct { │ │196 + repo: *rp.Repo(repo_kind, repo_opts), │ │197 + db: *DB, │ │198 + io: std.Io, │ │199 + allocator: std.mem.Allocator, │ │200 + ref: rf.Ref, │ │201 + │ │202 + pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │203 + var moment = try DB.HashMap(.read_write).init(cursor.*); │ │204 + var repo_moment = try ctx.repo.core.latestMoment(); │ │205 + const read_state = rp.Repo(repo_kind, repo_opts).State(.read_only){ .core = &ctx.repo.core, .extra = .{ .moment = &repo_moment } };│ │206 + try consumeInTransaction(repo_kind, repo_opts, read_state, ctx.db, &moment, ctx.io, ctx.allocator, ctx.ref); │ │207 + } │ │208 + }; │ │209 + │ │210 + try self.file.lock(io, .exclusive); │ │211 + defer self.file.unlock(io); │ │212 + │ │213 + const history = try DB.ArrayList(.read_write).init(self.db.rootCursor()); │ │214 + try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │215 + .repo = repo, │ │216 + .db = self.db, │ │217 + .io = io, │ │218 + .allocator = allocator, │ │219 + .ref = ref, │ │220 + }); │ │221 + } │ │222 + }; │ │223 } │ │224 + │ │225 +// bring a local repo's event db up to date with its events branch │ │226 +pub fn syncLocalEvents( │ │227 + comptime repo_kind: rp.RepoKind, │ │228 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │229 + io: std.Io, │ │230 + allocator: std.mem.Allocator, │ │231 + repo: *rp.Repo(repo_kind, repo_opts), │ │232 +) !void { │ │233 + var remotes = try repo.listRemotes(io, allocator); │ │234 + defer remotes.deinit(); │ │235 │ │236 + const ref: rf.Ref, const tip: [hash.hexLen(repo_opts.hash)]u8 = blk: { │ │237 + if (try repo.readRef(io, events_ref)) |oid| break :blk .{ events_ref, oid }; │ │238 + for (remotes.sections.keys()) |remote_name| { │ │239 + const remote_ref: rf.Ref = .{ .kind = .{ .remote = remote_name }, .name = events_ref.name }; │ │240 + if (try repo.readRef(io, remote_ref)) |oid| break :blk .{ remote_ref, oid }; │ │241 + } │ │242 + return; │ │243 + }; │ │244 + │ │245 + var event_db = try LocalEventDB(repo_opts.hash).create(io, allocator, repo.core.repo_dir); │ │246 + defer event_db.deinit(io, allocator); │ │247 + │ │248 + if (try lastConsumedOid(repo_opts.hash, event_db.db)) |last_oid| { │ │249 + var tip_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │250 + _ = try std.fmt.hexToBytes(&tip_bytes, &tip); │ │251 + if (std.mem.eql(u8, &last_oid, &tip_bytes)) return; │ │252 + } │ │253 + │ │254 + try event_db.consume(repo_kind, repo_opts, io, allocator, repo, ref); │ │255 +} │ │256 + │ │257 +// consume the events on `ref` into the db `moment` writes to. events are read │ │258 +// from `read_state`'s repo, which needn't be the repo backing the db — that's │ │259 +// how a local (possibly git-backed) repo's events land in a standalone db. │ │260 pub fn consumeInTransaction( │ │128 - comptime repo_opts: rp.RepoOpts(.xit), │ │129 - state: rp.Repo(.xit, repo_opts).State(.read_write), │ │261 + comptime repo_kind: rp.RepoKind, │ │262 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │263 + read_state: rp.Repo(repo_kind, repo_opts).State(.read_only), │ │264 + db: *EventDB(repo_opts.hash), │ │265 + moment: *EventDB(repo_opts.hash).HashMap(.read_write), │ │266 io: std.Io, │ │267 allocator: std.mem.Allocator, │ │268 ref: rf.Ref, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │269 ) !void { │ │134 - const DB = rp.Repo(.xit, repo_opts).DB; │ │270 + const DB = EventDB(repo_opts.hash); │ │271 │ │272 // the last_object_id represents the object id that was last consumed│ │273 var last_object_id_maybe: ?[hash.byteLen(repo_opts.hash)]u8 = null; │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │138 - if (try state.extra.moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id"))) |last_object_id_cursor| {│ │274 + if (try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id"))) |last_object_id_cursor| { │ │275 var last_object_id_buffer: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │276 _ = try last_object_id_cursor.readBytes(&last_object_id_buffer); │ │277 last_object_id_maybe = last_object_id_buffer; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │281 // the reason it is a list is so we can keep every previous haxy │ │282 // state, making it easy to revert to an older state if the user │ │283 // rebases some of the past commits. │ │148 - const haxy_cursor = try state.extra.moment.putCursor(hash.hashInt(repo_opts.hash, "haxy"));│ │284 + const haxy_cursor = try moment.putCursor(hash.hashInt(repo_opts.hash, "haxy")); │ │285 const haxy = try DB.ArrayList(.read_write).init(haxy_cursor); │ │286 │ │287 // add a new item to the haxy list created above. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │295 } │ │296 var haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │297 │ │162 - var commit_iter = try CommitIterator(repo_opts).init(state.readOnly(), io, allocator, haxy_moments.readOnly(), ref); │ │298 + var commit_iter = try CommitIterator(repo_kind, repo_opts).init(read_state, io, allocator, haxy_moments.readOnly(), ref);│ │299 defer commit_iter.deinit(io, allocator); │ │300 │ │301 // if there are no events to process, look at the oid at HEAD and update │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │303 // where we do a merge and then force-push to remove the merge. see the │ │304 // merge test for an example. │ │305 if (commit_iter.newest_object_id == null) { │ │170 - const head_oid_hex = (try rf.readRecur(.xit, repo_opts, state.readOnly(), io, .{ .ref = ref })) orelse return error.OidNotFound;│ │306 + const head_oid_hex = (try rf.readRecur(repo_kind, repo_opts, read_state, io, .{ .ref = ref })) orelse return error.OidNotFound; │ │307 var head_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │308 _ = try std.fmt.hexToBytes(&head_oid, &head_oid_hex); │ │309 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │314 const moment_index = try moment_index_cursor.readUint(); │ │315 │ │316 try haxy.slice(moment_index + 1); │ │181 - try state.extra.moment.put(hash.hashInt(repo_opts.hash, "haxy-last-object-id"), .{ .bytes = &head_oid });│ │317 + try moment.put(hash.hashInt(repo_opts.hash, "haxy-last-object-id"), .{ .bytes = &head_oid }); │ │318 return; │ │319 } │ │320 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │327 const newest_object_id = commit_iter.newest_object_id orelse return error.OidNotFound;│ │328 │ │329 _ = mrg.getDescendent( │ │194 - .xit, │ │330 + repo_kind, │ │331 repo_opts, │ │196 - state.readOnly(), │ │332 + read_state, │ │333 io, │ │334 allocator, │ │335 &std.fmt.bytesToHex(last_object_id, .lower), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯