╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │38 │ │39 const Self = @This(); │ │40 │ │41 -pub fn init( │ │42 - arena: *std.heap.ArenaAllocator, │ │43 - session: *ui.Session, │ │44 - source_maybe: ?ui.RepoSource, │ │45 - identity: []const u8, │ │46 - tag: []const u8, │ │47 - selected_id: []const u8, │ │48 -) !Self { │ │49 - const aa = arena.allocator(); │ │50 - const empty: Self = .{ │ │41 +// an empty listing, for the wasm / no-repo paths. │ │42 +pub fn emptyResult(aa: std.mem.Allocator, identity: []const u8, tag: []const u8, selected_id: []const u8) !Self {│ │43 + return .{ │ │44 .identity = try aa.dupe(u8, identity), │ │45 .tag = try aa.dupe(u8, tag), │ │46 .selected_id = try aa.dupe(u8, selected_id), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │47 .issues = &.{}, │ │48 .prev_id = null, │ │49 .next_id = null, │ │57 - }; │ │58 - │ │59 - // no filesystem (wasm) or nowhere to look: empty listing. the wasm path │ │60 - // never calls init anyway — it rebuilds from the serialized snapshot. │ │61 - const io = session.io orelse return empty; │ │62 - const source = source_maybe orelse return empty; │ │63 - │ │64 - // issues live in the repo's own xit db, so a git-backed repo has none. │ │65 - if (source.repo_kind == .git) return empty; │ │66 - │ │67 - // open with the arena's backing allocator (transient; the issue strings are │ │68 - // duped into the page arena so they outlive the repo handle). │ │69 - const gpa = arena.child_allocator; │ │70 - var any_repo = rp.AnyRepo(.xit, .{}).open(io, gpa, .{ .path = source.path }) catch return empty;│ │71 - defer any_repo.deinit(io, gpa); │ │72 - │ │73 - return switch (any_repo) { │ │74 - inline else => |*repo| collect(repo.self_repo_opts, arena, repo, empty), │ │50 }; │ │51 } │ │52 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │78 -// read one window of the repo's issues (filtered to empty.tag when set), │ │53 +// read one window of an opened repo's issues (filtered to `tag` when set), │ │54 // ordered by creation time (oldest first), starting at the issue │ │80 -// empty.selected_id names ("" = the beginning). │ │81 -fn collect( │ │82 - comptime repo_opts: rp.RepoOpts(.xit), │ │55 +// `selected_id` names ("" = the beginning). issues live in the repo's own │ │56 +// xit db, so a git-backed repo has none. │ │57 +pub fn init( │ │58 + comptime repo_kind: rp.RepoKind, │ │59 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │60 arena: *std.heap.ArenaAllocator, │ │84 - repo: *rp.Repo(.xit, repo_opts), │ │85 - empty: Self, │ │61 + repo: *rp.Repo(repo_kind, repo_opts), │ │62 + identity: []const u8, │ │63 + tag: []const u8, │ │64 + selected_id: []const u8, │ │65 ) !Self { │ │66 + const empty = try emptyResult(arena.allocator(), identity, tag, selected_id);│ │67 + if (repo_kind == .git) return empty; │ │68 + │ │69 const aa = arena.allocator(); │ │88 - const DB = rp.Repo(.xit, repo_opts).DB; │ │70 + const DB = rp.Repo(repo_kind, repo_opts).DB; │ │71 const rooted = empty.selected_id.len != 0; │ │72 const tagged = empty.tag.len != 0; │ │73 // an explicitly named issue or tag that doesn't exist is a bad url │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/ui/Repo/Refs.zig│ ╰────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │33 const Self = @This(); │ │34 │ │35 const Window = struct { names: []const []const u8, next_after: ?usize }; │ │36 + │ │37 +// empty columns, for the wasm / no-repo paths. │ │38 +pub fn emptyResult(arena: *std.heap.ArenaAllocator, identity: []const u8, kind: ui.RoutablePage.RefKind, after: usize) !Self {│ │39 + return .{ │ │40 + .identity = try arena.allocator().dupe(u8, identity), │ │41 + .kind = kind, │ │42 + .after = after, │ │43 + .branches = &.{}, │ │44 + .tags = &.{}, │ │45 + .branches_next_after = null, │ │46 + .tags_next_after = null, │ │47 + .branches_label = try ui.SubTitle.init(arena, "branches"), │ │48 + .tags_label = try ui.SubTitle.init(arena, "tags"), │ │49 + }; │ │50 +} │ │51 │ │52 +// read one window of each column from an opened repo. the ref names are duped │ │53 +// into the page arena so they outlive the repo handle. │ │54 pub fn init( │ │55 + comptime repo_kind: rp.RepoKind, │ │56 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │57 arena: *std.heap.ArenaAllocator, │ │39 - session: *ui.Session, │ │40 - source_maybe: ?ui.RepoSource, │ │58 + repo: *rp.Repo(repo_kind, repo_opts), │ │59 + io: std.Io, │ │60 + gpa: std.mem.Allocator, │ │61 identity: []const u8, │ │62 kind: ui.RoutablePage.RefKind, │ │63 after: usize, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 66 // the offset only applies to its own column; the other stays at 0. │ │ 67 const branches_after: usize = if (kind == .branch) after else 0; │ │ 68 const tags_after: usize = if (kind == .tag) after else 0; │ │ 49 - const branches_label = try ui.SubTitle.init(arena, "branches"); │ │ 50 - const tags_label = try ui.SubTitle.init(arena, "tags"); │ │ 51 - const empty: Self = .{ │ │ 52 - .identity = try aa.dupe(u8, identity), │ │ 53 - .kind = kind, │ │ 54 - .after = after, │ │ 55 - .branches = &.{}, │ │ 56 - .tags = &.{}, │ │ 57 - .branches_next_after = null, │ │ 58 - .tags_next_after = null, │ │ 59 - .branches_label = branches_label, │ │ 60 - .tags_label = tags_label, │ │ 61 - }; │ │ 62 - │ │ 63 - // no filesystem (wasm) or nowhere to look: empty lists. the wasm path never │ │ 64 - // calls init anyway — it rebuilds from the serialized snapshot. │ │ 65 - const io = session.io orelse return empty; │ │ 66 - const source = source_maybe orelse return empty; │ │ 67 - │ │ 68 - // open with the arena's backing allocator (transient; the ref names are │ │ 69 - // duped into the page arena so they outlive the repo handle). │ │ 70 - const gpa = arena.child_allocator; │ │ 71 - const branches, const tags = switch (source.repo_kind) { │ │ 72 - inline else => |repo_kind| blk: { │ │ 73 - var any_repo = rp.AnyRepo(repo_kind, .{}).open(io, gpa, .{ .path = source.path }) catch return empty; │ │ 74 - defer any_repo.deinit(io, gpa); │ │ 69 + var result = try emptyResult(arena, identity, kind, after); │ │ 70 │ │ 76 - switch (any_repo) { │ │ 77 - inline else => |*repo| { │ │ 78 - // xit seeks each iterator straight to its window; the git │ │ 79 - // backend can't seek, so it starts at the beginning and │ │ 80 - // collectWindow discards the entries before the window. │ │ 81 - const can_seek = repo_kind == .xit; │ │ 71 + // xit seeks each iterator straight to its window; the git backend can't │ │ 72 + // seek, so it starts at the beginning and collectWindow discards the │ │ 73 + // entries before the window. │ │ 74 + const can_seek = repo_kind == .xit; │ │ 75 │ │ 83 - var branch_iter = repo.listBranches(io, gpa, if (can_seek) .{ .index = branches_after } else .beginning) catch return empty;│ │ 84 - defer branch_iter.deinit(io); │ │ 85 - const b = try collectWindow(io, aa, &branch_iter, branches_after, if (can_seek) 0 else branches_after); │ │ 76 + var branch_iter = repo.listBranches(io, gpa, if (can_seek) .{ .index = branches_after } else .beginning) catch return result; │ │ 77 + defer branch_iter.deinit(io); │ │ 78 + const branches = try collectWindow(io, aa, &branch_iter, branches_after, if (can_seek) 0 else branches_after); │ │ 79 │ │ 87 - var tag_iter = repo.listTags(io, gpa, if (can_seek) .{ .index = tags_after } else .beginning) catch return empty; │ │ 88 - defer tag_iter.deinit(io); │ │ 89 - const t = try collectWindow(io, aa, &tag_iter, tags_after, if (can_seek) 0 else tags_after); │ │ 90 - break :blk .{ b, t }; │ │ 91 - }, │ │ 92 - } │ │ 93 - }, │ │ 94 - }; │ │ 80 + var tag_iter = repo.listTags(io, gpa, if (can_seek) .{ .index = tags_after } else .beginning) catch return result; │ │ 81 + defer tag_iter.deinit(io); │ │ 82 + const tags = try collectWindow(io, aa, &tag_iter, tags_after, if (can_seek) 0 else tags_after); │ │ 83 │ │ 96 - return .{ │ │ 97 - .identity = empty.identity, │ │ 98 - .kind = kind, │ │ 99 - .after = after, │ │100 - .branches = branches.names, │ │101 - .tags = tags.names, │ │102 - .branches_next_after = branches.next_after, │ │103 - .tags_next_after = tags.next_after, │ │104 - .branches_label = branches_label, │ │105 - .tags_label = tags_label, │ │106 - }; │ │ 84 + result.branches = branches.names; │ │ 85 + result.tags = tags.names; │ │ 86 + result.branches_next_after = branches.next_after; │ │ 87 + result.tags_next_after = tags.next_after; │ │ 88 + return result; │ │ 89 } │ │ 90 │ │ 91 // window a ref iterator without materializing the whole list. `skip` entries │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯