╭ changed ───────────────╮ │src/event/Discussion.zig│ ╰────────────────────────╯ ╭─────────────────────────────────────────────────────╮ │1 const std = @import("std"); │ │2 const evt = @import("../event.zig"); │ │3 -const srch = @import("../search_thread.zig"); │ │3 +const srch_thrd = @import("../search_thread.zig");│ │4 const xit = @import("xit"); │ │5 const rp = xit.repo; │ │6 const hash = xit.hash; │ ╰─────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 94 // the search index is keyed by creation, not activity: ordering is the │ │ 95 // list's business, so a comment moving the discussion up leaves it alone │ │ 96 const search_key = evt.orderKeyDesc(record_to_write.created_order, event_id); │ │ 97 - try srch.update(DB, hash_kind, .discuss, haxy_moment, arena.child_allocator, if (existing_record_maybe) |existing| srch.doc(&search_key, existing) else null, srch.doc(&search_key, record_to_write)); │ │ 97 + try srch_thrd.update(DB, hash_kind, .discuss, haxy_moment, arena.child_allocator, if (existing_record_maybe) |existing| srch_thrd.doc(&search_key, existing) else null, srch_thrd.doc(&search_key, record_to_write));│ │ 98 │ │ 99 if (!record_to_write.removed) { │ │100 try active.put(&order_key); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Issue.zig│ ╰───────────────────╯ ╭─────────────────────────────────────────────────────╮ │1 const std = @import("std"); │ │2 const evt = @import("../event.zig"); │ │3 const diff3 = @import("../diff3.zig"); │ │4 -const srch = @import("../search_thread.zig"); │ │4 +const srch_thrd = @import("../search_thread.zig");│ │5 const xit = @import("xit"); │ │6 const rp = xit.repo; │ │7 const hash = xit.hash; │ ╰─────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │141 } │ │142 │ │143 // the search index is keyed by the same order key as the sets above │ │144 - try srch.update(DB, hash_kind, .issue, haxy_moment, arena.child_allocator, if (existing_record_maybe) |existing| srch.doc(&order_key, existing) else null, srch.doc(&order_key, record_to_write)); │ │144 + try srch_thrd.update(DB, hash_kind, .issue, haxy_moment, arena.child_allocator, if (existing_record_maybe) |existing| srch_thrd.doc(&order_key, existing) else null, srch_thrd.doc(&order_key, record_to_write));│ │145 │ │146 if (!record_to_write.removed) { │ │147 const status_set = try statusSet(DB, status_to_issues, record_to_write.event.status); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Patch.zig│ ╰───────────────────╯ ╭─────────────────────────────────────────────────────╮ │1 const std = @import("std"); │ │2 const evt = @import("../event.zig"); │ │3 const diff3 = @import("../diff3.zig"); │ │4 -const srch = @import("../search_thread.zig"); │ │4 +const srch_thrd = @import("../search_thread.zig");│ │5 const xit = @import("xit"); │ │6 const rp = xit.repo; │ │7 const hash = xit.hash; │ ╰─────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │254 } │ │255 │ │256 // the search index is keyed by the same order key as the sets above │ │257 - try srch.update(DB, hash_kind, .patch, haxy_moment, arena.child_allocator, if (existing_maybe) |existing| srch.doc(&order_key, existing) else null, srch.doc(&order_key, record)); │ │257 + try srch_thrd.update(DB, hash_kind, .patch, haxy_moment, arena.child_allocator, if (existing_maybe) |existing| srch_thrd.doc(&order_key, existing) else null, srch_thrd.doc(&order_key, record));│ │258 │ │259 if (!record.removed) { │ │260 const status = try statusSet(DB, statuses, status_kind); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/find.zig│ ╰────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │107 } │ │108 } │ │109 │ │110 -// reconcile every branch tip's index. this runs outside the caller's │ │111 -// transaction, and reconciles unconditionally: an up-to-date branch costs one│ │112 -// ref read and an oid compare. │ │110 +// the action a file index refresh records when it runs on its own │ │111 +pub const undo_action = "haxy/find-index"; │ │112 + │ │113 +// reconcile every branch tip's index, in a transaction of its own │ │114 pub fn refresh( │ │115 comptime repo_opts: rp.RepoOpts(.xit), │ │116 io: std.Io, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │119 ) !void { │ │120 const Repo = rp.Repo(.xit, repo_opts); │ │121 const DB = Repo.DB; │ │122 + const Save = struct { │ │123 + core: *Repo.Core, │ │124 + io: std.Io, │ │125 + allocator: std.mem.Allocator, │ │126 + │ │127 + pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │128 + var moment = try DB.HashMap(.read_write).init(cursor.*); │ │129 + const state = Repo.State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │130 + if (!try refreshInTransaction(repo_opts, state, &moment, ctx.io, ctx.allocator)) return error.CancelTransaction; │ │131 + │ │132 + // record the user action for undo │ │133 + try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action_kind = undo_action } }); │ │134 + } │ │135 + }; │ │136 + │ │137 + // held across the reads and the writes, so a concurrent refresh can't │ │138 + // change the entries these decisions were made from. │ │139 + try repo.core.db_file.lock(io, .exclusive); │ │140 + defer repo.core.db_file.unlock(io); │ │141 + │ │142 + const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │143 + history.appendContext(.{ .slot = try history.getSlot(-1) }, Save{ .core = &repo.core, .io = io, .allocator = allocator }) catch |err| switch (err) {│ │144 + error.CancelTransaction => {}, │ │145 + else => return err, │ │146 + }; │ │147 +} │ │148 + │ │149 +// reconcile them inside the caller's transaction, so the index lands with the │ │150 +// push that invalidated it. every branch is reconciled, since an up-to-date │ │151 +// one costs a ref read and an oid compare. reports whether anything was │ │152 +// written, so a caller that owns an otherwise empty transaction can cancel it │ │153 +pub fn refreshInTransaction( │ │154 + comptime repo_opts: rp.RepoOpts(.xit), │ │155 + state: rp.Repo(.xit, repo_opts).State(.read_write), │ │156 + moment: *rp.Repo(.xit, repo_opts).DB.HashMap(.read_write), │ │157 + io: std.Io, │ │158 + allocator: std.mem.Allocator, │ │159 +) !bool { │ │160 + const Repo = rp.Repo(.xit, repo_opts); │ │161 + const DB = Repo.DB; │ │162 const HashInt = hash.HashInt(repo_opts.hash); │ │163 const Tree = [hash.hexLen(repo_opts.hash)]u8; │ │164 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │187 var work: std.ArrayList(Work) = .empty; │ │188 var base: ?Entry = null; │ │189 │ │149 - // held across the reads and the writes, so a concurrent refresh can't │ │150 - // change the entries these decisions were made from. │ │151 - try repo.core.db_file.lock(io, .exclusive); │ │152 - defer repo.core.db_file.unlock(io); │ │153 - │ │190 { │ │155 - var moment = try repo.core.latestMoment(); │ │156 - const state = Repo.State(.read_only){ .core = &repo.core, .extra = .{ .moment = &moment } };│ │191 + const read_state = state.readOnly(); │ │192 │ │193 // entries carry no branch name, so branches are matched by hash │ │194 var existing: std.AutoArrayHashMapUnmanaged(HashInt, Entry) = .empty; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │205 // else any other branch's, so only its own changes are written. │ │206 if (existing.count() > 0) base = existing.values()[0]; │ │207 var head_buffer: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; │ │173 - if (repo.head(io, &head_buffer)) |head| switch (head) { │ │208 + if (rf.readHead(.xit, repo_opts, read_state, io, &head_buffer) catch null) |head| switch (head) {│ │209 .ref => |ref| if (existing.get(hash.hashInt(repo_opts.hash, ref.name))) |entry| { │ │210 base = entry; │ │211 }, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯