╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │404 pub const RepoUndoRoute = struct { │ │405 name: Array(repo_identity_max_len), │ │406 index: ?u64 = null, │ │407 + // the clear-history confirmation page, which has no selected entry│ │408 + clear: bool = false, │ │409 }; │ │410 │ │411 pub fn repoUndoRoute(identity: []const u8, index: ?u64) ?RoutablePage {│ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │412 return .{ .repo_undo = .{ .name = Array(repo_identity_max_len).from(identity) orelse return null, .index = index } };│ │413 + } │ │414 + │ │415 + pub fn repoUndoClearRoute(identity: []const u8) ?RoutablePage { │ │416 + return .{ .repo_undo = .{ .name = Array(repo_identity_max_len).from(identity) orelse return null, .clear = true } }; │ │417 } │ │418 │ │419 pub const RepoEventsRoute = struct { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1164 }, │ │1165 .repo_undo => |u| blk: { │ │1166 const prefix = try repoUrlPrefix(arena, u.name.slice()); │ │1161 - 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});│ │1167 + break :blk if (u.clear) │ │1168 + try std.fmt.allocPrint(arena.allocator(), "{s}/undo/clear", .{prefix}) │ │1169 + else if (u.index) |index| │ │1170 + try std.fmt.allocPrint(arena.allocator(), "{s}/undo/{d}", .{ prefix, index }) │ │1171 + else │ │1172 + try std.fmt.allocPrint(arena.allocator(), "{s}/undo", .{prefix}); │ │1173 }, │ │1174 .repo_events => |e| blk: { │ │1175 const prefix = try repoUrlPrefix(arena, e.name.slice()); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1566 return null; │ │1567 } │ │1568 if (std.mem.eql(u8, tab, "undo")) { │ │1558 - const index = if (segments.next()) |word| std.fmt.parseInt(u64, word, 10) catch return null else null;│ │1559 - return if (segments.next() == null) repoUndoRoute(pair, index) else null; │ │1569 + const word = segments.next(); │ │1570 + if (segments.next() != null) return null; │ │1571 + const value = word orelse return repoUndoRoute(pair, null); │ │1572 + if (std.mem.eql(u8, value, "clear")) return repoUndoClearRoute(pair); │ │1573 + return repoUndoRoute(pair, std.fmt.parseInt(u64, value, 10) catch return null); │ │1574 } │ │1575 if (std.mem.eql(u8, tab, "events")) { │ │1576 const word = params.scanTail(&segments) catch return null; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1712 a_p.view == b.repo_patches.view and │ │1713 a_p.comments_start == b.repo_patches.comments_start and │ │1714 std.mem.eql(u8, a_p.comment.slice(), b.repo_patches.comment.slice()), │ │1701 - .repo_undo => |a_u| std.mem.eql(u8, a_u.name.slice(), b.repo_undo.name.slice()) and a_u.index == b.repo_undo.index,│ │1715 + .repo_undo => |a_u| std.mem.eql(u8, a_u.name.slice(), b.repo_undo.name.slice()) and │ │1716 + a_u.index == b.repo_undo.index and │ │1717 + a_u.clear == b.repo_undo.clear, │ │1718 .repo_events => |a_e| std.mem.eql(u8, a_e.name.slice(), b.repo_events.name.slice()) and │ │1719 a_e.view == b.repo_events.view and │ │1720 a_e.moment == b.repo_events.moment and │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │265 // a server shows events to whoever it shows undo to, so the route follows │ │266 if (route == .repo_events and session.data.host_kind != .local and !undo_allowed) return error.NotFound;│ │267 const undo_index = if (route == .repo_undo) route.repo_undo.index else null; │ │268 + const undo_clear = route == .repo_undo and route.repo_undo.clear; │ │269 // an unreadable repo still shows the tab, like every other tab │ │270 var undo_data: ?Undo = if (undo_allowed) .{ .identity = repo_identity.identity } else null; │ │271 const files, const changes, const refs, var issues, var patches, var discussions, const events = blk: { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │327 try Events.empty(aa, repo_identity.identity, events_view, session.local != null, session.data.sync_failure),│ │328 }; │ │329 }; │ │329 - if (undo_data) |*undo| undo.can_undo = session.local != null or evt.Repo.roleOf(repo, session.userId()) == .owner; │ │330 + if (undo_data) |*undo| { │ │331 + undo.can_undo = session.local != null or evt.Repo.roleOf(repo, session.userId()) == .owner; │ │332 + undo.clear = undo_clear; │ │333 + } │ │334 if (route == .repo_undo and undo_data == null) return error.NotFound; │ │335 issues.repo_source = source; │ │336 patches.repo_source = source; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │519 if (stack.getSelected()) |selected_widget| switch (selected_widget.*) {│ │520 .repo_files => |*v| if (v.focusHeader(root_focus)) return, │ │521 .repo_commits => |*v| if (v.focusHeader(root_focus)) return, │ │522 + .repo_undo => |*v| if (v.focusHeader(root_focus)) return, │ │523 else => {}, │ │524 }; │ │525 index = stack_index; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/ui/Repo/Undo.zig│ ╰────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │37 next: ?u64 = null, │ │38 failure: ?[]const u8 = null, │ │39 can_undo: bool = false, │ │40 +// the clear-history confirmation stands in for the list │ │41 +clear: bool = false, │ │42 │ │43 pub fn init(comptime opts: rp.RepoOpts(.xit), arena: *std.heap.ArenaAllocator, repo: *rp.Repo(.xit, opts), identity: []const u8, selected: ?u64) !Self {│ │44 const aa = arena.allocator(); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │142 } │ │143 } │ │144 │ │145 +// discard every earlier transaction by compacting the database. the patch │ │146 +// revisions events point at are unreachable from the refs, so they are named │ │147 +// as extra roots to survive the collection. │ │148 +pub fn clearHistory(io: std.Io, allocator: std.mem.Allocator, source: ui.RepoSource) !void { │ │149 + if (source.repo_kind != .xit) return error.NotFound; │ │150 + var any_repo = try rp.AnyRepo(.xit, .{}).open(io, allocator, source.localInitOpts()); │ │151 + defer any_repo.deinit(io, allocator); │ │152 + switch (any_repo) { │ │153 + inline else => |*repo| { │ │154 + const opts = repo.self_repo_opts; │ │155 + // a repo that has consumed no events has no revisions to protect │ │156 + const roots = if (evt.currentMoment(opts, repo)) |moment| │ │157 + try evt.PatchRev.gcRoots(rp.Repo(.xit, opts).DB, opts.hash, allocator, moment) │ │158 + else |_| │ │159 + &.{}; │ │160 + defer allocator.free(roots); │ │161 + _ = try repo.garbageCollect(io, allocator, .{ .extra_roots = roots }); │ │162 + }, │ │163 + } │ │164 +} │ │165 + │ │166 // keep action failures on the current tab without rebuilding a stale route. │ │167 pub fn handleRequest(allocator: std.mem.Allocator, session: *ui.Session, target: ui.RoutablePage.RepoUndoRoute) void {│ │168 perform(allocator, session, target) catch |err| { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯