╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │68 const parent = if (try rf.readRecur(repo_kind, repo_opts, state.readOnly(), ctx.io, .{ .ref = evt.events_ref }) == null) ctx.first_parent else null; │ │69 try evt.commitEvents(repo_kind, repo_opts, state, ctx.io, ctx.arena.child_allocator, evt.events_ref, &events, parent); │ │70 if (!try evt.consumeInTransaction(.repo, repo_kind, repo_opts, state, &ctx.repo.core.db, &moment, ctx.io, ctx.arena.child_allocator, evt.events_ref)) return error.CancelTransaction;│ │71 - try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action = "update_branch_patch" } }); │ │71 + try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action = "update_patch" } }); │ │72 } │ │73 }; │ │74 { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │291 continue; │ │292 }, │ │293 .sync_events => {}, │ │294 + .undo => |target| try ui.Repo.Undo.perform(allocator, &ui_session, target),│ │295 } │ │296 } │ │297 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/test.zig│ ╰────────────╯ ╭───────────────────────────────────────────╮ │4 _ = @import("test/event/patch.zig");│ │5 _ = @import("test/ssh.zig"); │ │6 _ = @import("test/ui.zig"); │ │7 + _ = @import("test/undo.zig"); │ │8 } │ │9 │ ╰───────────────────────────────────────────╯ ╭ added ──────────╮ │src/test/undo.zig│ ╰─────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │826 // fixtures commit directly, so index the ref tips by hand │ │827 try find.refresh(.{}, io, allocator, &template_repo); │ │828 try cms.refresh(.{}, io, allocator, &template_repo, null); │ │829 + // clear history only after indexing has written its transactions. │ │830 + { │ │831 + const moment = try evt.currentMoment(.{}, &template_repo); │ │832 + const roots = try evt.PatchRev.gcRoots(rp.Repo(.xit, .{}).DB, .sha1, allocator, moment);│ │833 + defer allocator.free(roots); │ │834 + _ = try template_repo.garbageCollect(io, allocator, .{ .extra_roots = roots }); │ │835 + } │ │836 } │ │837 │ │838 // copy the template to each repo's on-disk location, named by its │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │1079 try fork_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "true" });│ │1080 // the commits above skipped the push path, so index the patch branch by hand │ │1081 try find.refresh(.{}, io, allocator, &fork_repo); │ │1082 + _ = try fork_repo.garbageCollect(io, allocator, .{}); │ │1083 try fork_dir.deleteFile(io, path); │ │1084 const revision_id = evt.EventWithId.randomId(random); │ │1085 const head_tree_oid = try commitTree(io, allocator, &fork_repo, &source_oid); │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │87 }; │ │88 }, │ │89 .repo => switch (route) { │ │90 - .repo_files, .repo_commits, .repo_diff, .repo_refs, .repo_issues, .repo_patches, .repo_discussions, .repo_events, .repo_settings, .repo_auth => .{ .repo = try Repo.init(arena, session, route) }, │ │90 + .repo_files, .repo_commits, .repo_diff, .repo_refs, .repo_issues, .repo_patches, .repo_discussions, .repo_events, .repo_undo, .repo_settings, .repo_auth => .{ .repo = try Repo.init(arena, session, route) },│ │91 else => return error.UnexpectedRoute, │ │92 }, │ │93 .fork => switch (route) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────╮ │168 comment: Array(evt.event_id_size * 2) = .{},│ │169 }, │ │170 repo_events: RepoEventsRoute, │ │171 + repo_undo: RepoUndoRoute, │ │172 repo_settings: Array(repo_route_max_len), │ │173 repo_auth: Array(repo_route_max_len), │ │174 fork_patch: ForkRoute, │ ╰─────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │395 }; │ │396 }; │ │397 │ │398 + pub const RepoUndoRoute = struct { │ │399 + name: Array(repo_identity_max_len), │ │400 + index: ?u64 = null, │ │401 + }; │ │402 + │ │403 + pub fn repoUndoRoute(identity: []const u8, index: ?u64) ?RoutablePage { │ │404 + return .{ .repo_undo = .{ .name = Array(repo_identity_max_len).from(identity) orelse return null, .index = index } };│ │405 + } │ │406 + │ │407 pub const RepoEventsRoute = struct { │ │408 name: Array(repo_identity_max_len), │ │409 view: EventsView = .active, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1146 try out.writer.print("{s}/discussions/{s}", .{ prefix, @tagName(t.view) }); │ │1147 try writeListFilters(&out.writer, t.tag.slice(), t.search.slice()); │ │1148 break :blk out.written(); │ │1149 + }, │ │1150 + .repo_undo => |u| blk: { │ │1151 + const prefix = try repoUrlPrefix(arena, u.name.slice()); │ │1152 + break :blk if (u.index) |index| try std.fmt.allocPrint(arena.allocator(), "{s}/undo/{d}", .{ prefix, index }) else try std.fmt.allocPrint(arena.allocator(), "{s}/undo", .{prefix});│ │1153 }, │ │1154 .repo_events => |e| blk: { │ │1155 const prefix = try repoUrlPrefix(arena, e.name.slice()); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯