╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───╮ │src/find.zig│ ╰────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │212 .oid => {}, │ │178 - } else |_| {} │ │213 + }; │ │214 │ │180 - var branches = try repo.listBranches(io, allocator, .beginning); │ │215 + var branches = try rf.RefIterator(.xit, repo_opts).init(read_state, io, allocator, .head, .beginning);│ │216 defer branches.deinit(); │ │217 while (try branches.next()) |ref| { │ │218 if (std.mem.eql(u8, ref.name, evt.events_ref.name)) continue; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │219 const branch_hash = hash.hashInt(repo_opts.hash, ref.name); │ │220 const own = if (existing.fetchSwapRemove(branch_hash)) |kv| kv.value else null; │ │186 - const oid = (try repo.readRef(io, .{ .kind = .head, .name = ref.name })) orelse continue; │ │187 - const tree = try rootTreeOid(repo_opts, state, io, allocator, &oid); │ │221 + const oid = (try rf.readRecur(.xit, repo_opts, read_state, io, .{ .ref = .{ .kind = .head, .name = ref.name } })) orelse continue;│ │222 + const tree = try rootTreeOid(repo_opts, read_state, io, allocator, &oid); │ │223 if (own) |entry| if (std.mem.eql(u8, &entry.tree, &tree)) continue; │ │224 try work.append(aa, .{ .branch_hash = branch_hash, .tree = tree, .own = own }); │ │225 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │228 for (existing.keys()) |branch_hash| try work.append(aa, .{ .branch_hash = branch_hash, .tree = null, .own = null }); │ │229 } │ │230 │ │196 - if (work.items.len == 0) return; │ │231 + if (work.items.len == 0) return false; │ │232 │ │198 - const Save = struct { │ │199 - core: *Repo.Core, │ │200 - io: std.Io, │ │201 - allocator: std.mem.Allocator, │ │202 - work: []const Work, │ │203 - base: ?Entry, │ │233 + { │ │234 + const index_hash = hash.hashInt(repo_opts.hash, index_key); │ │235 + var new_branch_base = base; │ │236 │ │205 - pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │206 - var moment = try DB.HashMap(.read_write).init(cursor.*); │ │207 - const state = Repo.State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │208 - const index_hash = hash.hashInt(repo_opts.hash, index_key); │ │209 - var new_branch_base = ctx.base; │ │237 + var key: std.ArrayList(u8) = .empty; │ │238 + defer key.deinit(allocator); │ │239 │ │211 - var key: std.ArrayList(u8) = .empty; │ │212 - defer key.deinit(ctx.allocator); │ │213 - │ │214 - for (ctx.work) |item| { │ │215 - const index = try DB.HashMap(.read_write).init(try moment.putCursor(index_hash)); │ │216 - const tree = item.tree orelse { │ │217 - _ = try index.remove(item.branch_hash); │ │218 - continue; │ │219 - }; │ │220 - // the entry this one starts from (null = index the whole tree) │ │221 - const from = item.own orelse new_branch_base; │ │240 + for (work.items) |item| { │ │241 + const index = try DB.HashMap(.read_write).init(try moment.putCursor(index_hash)); │ │242 + const tree = item.tree orelse { │ │243 + _ = try index.remove(item.branch_hash); │ │244 + continue; │ │245 + }; │ │246 + // the entry this one starts from (null = index the whole tree) │ │247 + const from = item.own orelse new_branch_base; │ │248 │ │223 - var tree_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │224 - _ = try std.fmt.hexToBytes(&tree_bytes, &tree); │ │225 - // putKey only fills an empty key slot, and this one changes │ │226 - // whenever the branch moves │ │227 - var tree_cursor = try index.putKeyCursor(item.branch_hash); │ │228 - try tree_cursor.write(.{ .bytes = &tree_bytes }); │ │229 - var files_cursor = try index.putCursor(item.branch_hash); │ │230 - if (from) |entry| try files_cursor.write(.{ .slot = entry.files }); │ │231 - const files = try DB.SortedMap(.read_write).init(files_cursor); │ │249 + var tree_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │250 + _ = try std.fmt.hexToBytes(&tree_bytes, &tree); │ │251 + // putKey only fills an empty key slot, and this one changes │ │252 + // whenever the branch moves │ │253 + var tree_cursor = try index.putKeyCursor(item.branch_hash); │ │254 + try tree_cursor.write(.{ .bytes = &tree_bytes }); │ │255 + var files_cursor = try index.putCursor(item.branch_hash); │ │256 + if (from) |entry| try files_cursor.write(.{ .slot = entry.files }); │ │257 + const files = try DB.SortedMap(.read_write).init(files_cursor); │ │258 │ │233 - if (from) |entry| { │ │234 - var tree_diff = tr.TreeDiff(.xit, repo_opts).init(ctx.allocator); │ │235 - defer tree_diff.deinit(); │ │236 - try tree_diff.compare(state.readOnly(), ctx.io, &entry.tree, &tree, null); │ │237 - for (tree_diff.changes.keys(), tree_diff.changes.values()) |path, change| { │ │238 - try fileKey(ctx.allocator, &key, path); │ │239 - const oid = if (change.new) |*tree_entry| indexedOid(repo_opts.hash, tree_entry) else null; │ │240 - if (oid) |bytes| { │ │241 - try files.put(key.items, .{ .bytes = bytes }); │ │242 - } else { │ │243 - _ = try files.remove(key.items); │ │244 - } │ │259 + if (from) |entry| { │ │260 + var tree_diff = tr.TreeDiff(.xit, repo_opts).init(allocator); │ │261 + defer tree_diff.deinit(); │ │262 + try tree_diff.compare(state.readOnly(), io, &entry.tree, &tree, null); │ │263 + for (tree_diff.changes.keys(), tree_diff.changes.values()) |path, change| { │ │264 + try fileKey(allocator, &key, path); │ │265 + const oid = if (change.new) |*tree_entry| indexedOid(repo_opts.hash, tree_entry) else null; │ │266 + if (oid) |bytes| { │ │267 + try files.put(key.items, .{ .bytes = bytes }); │ │268 + } else { │ │269 + _ = try files.remove(key.items); │ │270 } │ │246 - } else { │ │247 - var paths = std.heap.ArenaAllocator.init(ctx.allocator); │ │248 - defer paths.deinit(); │ │249 - try indexTree(repo_opts, state.readOnly(), ctx.io, ctx.allocator, paths.allocator(), files, &key, "", &tree); │ │271 } │ │251 - │ │252 - // the first entry ever written becomes the base for the next │ │253 - // new branch, so only one branch walks a whole tree. │ │254 - if (new_branch_base == null) new_branch_base = .{ .tree = tree, .files = (try index.getSlot(item.branch_hash)) orelse unreachable };│ │255 - // a later entry copies an earlier one's slot, so everything │ │256 - // written so far has to be shared rather than mutated │ │257 - try ctx.core.db.freeze(); │ │272 + } else { │ │273 + var paths = std.heap.ArenaAllocator.init(allocator); │ │274 + defer paths.deinit(); │ │275 + try indexTree(repo_opts, state.readOnly(), io, allocator, paths.allocator(), files, &key, "", &tree); │ │276 } │ │259 - } │ │260 - }; │ │277 │ │262 - const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │263 - try history.appendContext(.{ .slot = try history.getSlot(-1) }, Save{ │ │264 - .core = &repo.core, │ │265 - .io = io, │ │266 - .allocator = allocator, │ │267 - .work = work.items, │ │268 - .base = base, │ │269 - }); │ │278 + // the first entry ever written becomes the base for the next │ │279 + // new branch, so only one branch walks a whole tree. │ │280 + if (new_branch_base == null) new_branch_base = .{ .tree = tree, .files = (try index.getSlot(item.branch_hash)) orelse unreachable }; │ │281 + // a later entry copies an earlier one's slot, so everything │ │282 + // written so far has to be shared rather than mutated │ │283 + try state.core.db.freeze(); │ │284 + } │ │285 + } │ │286 + return true; │ │287 } │ │288 │ │289 // insert every file under `oid` (a tree), as tr.Tree.read walks one. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭──────────────────────────────────────────────────╮ │1 const std = @import("std"); │ │2 const evt = @import("event.zig"); │ │3 const find = @import("find.zig"); │ │4 -const cms = @import("search_commit.zig"); │ │4 +const srch_cmmt = @import("search_commit.zig");│ │5 const xit = @import("xit"); │ │6 const rp = xit.repo; │ │7 const hash = xit.hash; │ ╰──────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │144 _ = try moment.remove(hash.hashInt(repo_opts.hash, evt.last_object_id_key)); │ │145 // forks aren't searched, so the copied versions would linger │ │146 // with nothing to prune them │ │147 - _ = try moment.remove(hash.hashInt(repo_opts.hash, cms.index_key)); │ │147 + _ = try moment.remove(hash.hashInt(repo_opts.hash, srch_cmmt.index_key)); │ │148 │ │149 if (head_oid_maybe) |*head_oid| { │ │150 try rf.write(.xit, repo_opts, state, ctx.io, patch_path, .{ .oid = head_oid });│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────╮ │ 7 const rf = xit.ref; │ │ 8 const mrg = xit.merge; │ │ 9 const find = @import("find.zig"); │ │10 -const cms = @import("search_commit.zig"); │ │10 +const srch_cmmt = @import("search_commit.zig"); │ │11 const fork = @import("fork.zig"); │ │12 const serve_common = @import("serve_common.zig");│ │13 │ ╰─────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │14 // the action a patch merge records │ │15 pub const merge_undo_action = "haxy/merge"; │ │16 + │ │17 +// the action a mergeability recheck records │ │18 +pub const mergeability_undo_action = "haxy/mergeability"; │ │19 │ │20 // progress is optional and best effort: a failed report must not fail a refresh │ │21 fn reportProgress(comptime repo_opts: rp.RepoOpts(.xit), io: std.Io, progress_ctx_maybe: ?repo_opts.ProgressCtx, event: rp.ProgressEvent) void {│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │597 var moment = try DB.HashMap(.read_write).init(cursor.*); │ │598 const state = Repo.State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │599 if (!try ctx.checks.update(state, ctx.io, ctx.allocator)) return error.CancelTransaction; │ │600 + │ │601 + // name the transaction, which no user action owns │ │602 + try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action_kind = mergeability_undo_action } });│ │603 } │ │604 }; │ │605 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │1166 find.refresh(repo_opts, io, allocator, target_repo) catch |err| { │ │1167 std.log.warn("failed to refresh file index: {s}", .{@errorName(err)}); │ │1168 }; │ │1163 - cms.refresh(repo_opts, io, allocator, target_repo, null) catch |err| { │ │1169 + srch_cmmt.refresh(repo_opts, io, allocator, target_repo, null) catch |err| {│ │1170 std.log.warn("failed to refresh commit index: {s}", .{@errorName(err)});│ │1171 }; │ │1172 } │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/push.zig│ ╰────────────╯ ╭─────────────────────────────────────────────────────╮ │ 8 const evt = @import("event.zig"); │ │ 9 const pch = @import("patch.zig"); │ │10 const find = @import("find.zig"); │ │11 -const cms = @import("search_commit.zig"); │ │11 +const srch_cmmt = @import("search_commit.zig"); │ │12 const fork = @import("fork.zig"); │ │13 const serve_common = @import("serve_common.zig");│ │14 │ ╰─────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯