╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │530 }; │ │531 } │ │532 │ │533 +fn setForkAvailability(arena: *std.heap.ArenaAllocator, admin_moment: ?evt.AdminDB.HashMap(.read_only), target: *Window) !void {│ │534 + const moment = admin_moment orelse return; │ │535 + if (target.items.len == 0) return; │ │536 + const items = try arena.allocator().dupe(PatchWithId, target.items); │ │537 + for (items) |*item| { │ │538 + const id = try evt.parseEventId(item.id); │ │539 + const record = try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, &id); │ │540 + item.fork_exists = if (record) |value| !value.removed else false; │ │541 + } │ │542 + target.items = items; │ │543 +} │ │544 + │ │545 fn loadDraftWindow( │ │546 comptime repo_kind: rp.RepoKind, │ │547 comptime repo_opts: rp.RepoOpts(repo_kind), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │603 const path = try fork.forkPath(aa, repos_dir, &id_hex); │ │604 var fork_repo = rp.Repo(.xit, .{}).open(io, arena.child_allocator, .{ .path = path, .require_repo_root = true }) catch continue; │ │605 defer fork_repo.deinit(io, arena.child_allocator); │ │565 - const fork_oid = (try fork_repo.readRef(io, fork.ref)) orelse continue; │ │566 - const moment = evt.currentMoment(.{}, &fork_repo) catch continue; │ │567 - const patch = (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, moment, arena, &id)) orelse continue; │ │568 - var revision_ready = false; │ │569 - var target_branch = default_target_branch; │ │570 - if (patch.event.revision) |revision| { │ │571 - const target_ref = rf.Ref.initFromPath(revision.target_ref, null) orelse continue; │ │572 - target_branch = switch (target_ref.kind) { │ │573 - .head => target_ref.name, │ │574 - else => continue, │ │575 - }; │ │576 - const revision_id = evt.parseEventId(&revision.id) catch continue; │ │577 - if (try evt.PatchRev.readById(evt.EventDB(.sha1), .sha1, moment, arena, &revision_id)) |record| │ │578 - revision_ready = revision.matches(record); │ │579 - } │ │580 - const target_oid = if (target_branch.len != 0) │ │581 - try target_repo.readRef(io, .{ .kind = .head, .name = target_branch }) │ │582 - else │ │583 - null; │ │584 - try items.append(aa, .{ │ │585 - .id = try aa.dupe(u8, &id_hex), │ │586 - .record = patch, │ │587 - .author = try ui.Author.initFromEmail(admin_moment, arena, patch.author_email), │ │588 - .draft = true, │ │589 - .revision_ready = revision_ready, │ │590 - .fork_oid = try aa.dupe(u8, &fork_oid), │ │591 - .target_branch = try aa.dupe(u8, target_branch), │ │592 - .no_changes = if (target_oid) |oid| std.mem.eql(u8, &oid, &fork_oid) else false, │ │593 - }); │ │606 + const entry = (try loadDraftEntry(repo_kind, repo_opts, arena, io, admin_moment, &fork_repo, target_repo, id, default_target_branch)) orelse continue;│ │607 + try items.append(aa, entry); │ │608 } │ │609 return .{ │ │610 .items = items.items, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │614 }; │ │615 } │ │616 │ │617 +pub fn loadDraftEntry( │ │618 + comptime repo_kind: rp.RepoKind, │ │619 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │620 + arena: *std.heap.ArenaAllocator, │ │621 + io: std.Io, │ │622 + admin_moment: ?evt.AdminDB.HashMap(.read_only), │ │623 + fork_repo: *rp.Repo(.xit, .{}), │ │624 + target_repo: *rp.Repo(repo_kind, repo_opts), │ │625 + id: [evt.event_id_size]u8, │ │626 + default_target_branch: []const u8, │ │627 +) !?PatchWithId { │ │628 + const aa = arena.allocator(); │ │629 + const id_hex = std.fmt.bytesToHex(id, .lower); │ │630 + const fork_oid = (try fork_repo.readRef(io, fork.ref)) orelse return null; │ │631 + const moment = evt.currentMoment(.{}, fork_repo) catch return null; │ │632 + const patch = (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, moment, arena, &id)) orelse return null;│ │633 + var revision_ready = false; │ │634 + var target_branch = default_target_branch; │ │635 + if (patch.event.revision) |revision| { │ │636 + target_branch = revision.targetBranch() orelse return null; │ │637 + const revision_id = evt.parseEventId(&revision.id) catch return null; │ │638 + if (try evt.PatchRev.readById(evt.EventDB(.sha1), .sha1, moment, arena, &revision_id)) |record| │ │639 + revision_ready = revision.matches(record); │ │640 + } │ │641 + const target_oid = if (target_branch.len != 0) │ │642 + try target_repo.readRef(io, .{ .kind = .head, .name = target_branch }) │ │643 + else │ │644 + null; │ │645 + return .{ │ │646 + .id = try aa.dupe(u8, &id_hex), │ │647 + .record = patch, │ │648 + .author = try ui.Author.initFromEmail(admin_moment, arena, patch.author_email), │ │649 + .draft = true, │ │650 + .revision_ready = revision_ready, │ │651 + .fork_oid = try aa.dupe(u8, &fork_oid), │ │652 + .target_branch = try aa.dupe(u8, target_branch), │ │653 + .no_changes = if (target_oid) |oid| std.mem.eql(u8, &oid, &fork_oid) else false, │ │654 + .fork_exists = true, │ │655 + }; │ │656 +} │ │657 + │ │658 pub const View = thread.View(.patch, Self); │ │659 │ │660 pub const Header = thread.Header; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/User.zig│ ╰───────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │21 pub const page_size = 20; // how many repos one window of the repos tab shows│ │22 │ │23 pub const ForkItem = struct { │ │24 + id: []const u8, │ │25 target: []const u8, │ │26 title: []const u8, │ │27 }; │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │118 var order_key: [@sizeOf(u64) + evt.event_id_size]u8 = undefined; │ │119 _ = try kv.key_cursor.readBytes(&order_key); │ │120 const fork_id = order_key[@sizeOf(u64)..]; │ │121 + const fork_id_hex = std.fmt.bytesToHex(fork_id.*, .lower); │ │122 const record = (try evt.Fork.readById(DB, hash_kind, haxy_moment, arena, fork_id)) orelse continue; │ │123 │ │124 const repo_cursor = try repo_records.getCursor(hash.hashInt(hash_kind, record.event.repo_id)) orelse continue;│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │128 │ │129 var title: []const u8 = "(unavailable)"; │ │130 if (session.io) |io| if (session.repos_dir) |repos_dir| { │ │129 - const id_hex = std.fmt.bytesToHex(fork_id.*, .lower); │ │130 - const path = try fork.forkPath(arena.allocator(), repos_dir, &id_hex); │ │131 + const path = try fork.forkPath(arena.allocator(), repos_dir, &fork_id_hex); │ │132 if (rp.Repo(.xit, .{}).open(io, arena.child_allocator, .{ .path = path, .require_repo_root = true })) |opened| {│ │133 var fork_repo = opened; │ │134 defer fork_repo.deinit(io, arena.child_allocator); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │137 } else |_| {} │ │138 } else |_| {} │ │139 }; │ │139 - try forks.append(arena.allocator(), .{ .target = target, .title = title });│ │140 + try forks.append(arena.allocator(), .{ │ │141 + .id = try arena.allocator().dupe(u8, &fork_id_hex), │ │142 + .target = target, │ │143 + .title = title, │ │144 + }); │ │145 } │ │146 forks_next_start = if (end < count) end else null; │ │147 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │224 var items: std.ArrayList(ui.widget.FlowBox.Item) = .empty; │ │225 if (data.forks_start > 0) │ │226 try items.append(aa, .{ .text = "← previous", .link = try std.fmt.allocPrint(aa, "a:/user/{s}/forks/start:{d}", .{ data.user.name, data.forks_start -| page_size }) });│ │222 - for (data.forks) |fork_item| │ │223 - try items.append(aa, .{ .text = try std.fmt.allocPrint(aa, "{s}\n{s}", .{ fork_item.target, fork_item.title }), .link = "" }); │ │227 + for (data.forks) |fork_item| { │ │228 + const route = ui.RoutablePage.forkPatchRoute(fork_item.target, fork_item.id) orelse return error.RouteTooLong; │ │229 + try items.append(aa, .{ │ │230 + .text = try std.fmt.allocPrint(aa, "{s}\n{s}", .{ fork_item.target, fork_item.title }), │ │231 + .link = try std.fmt.allocPrint(aa, "a:{s}", .{try route.toUrl(session.page_arena)}), │ │232 + }); │ │233 + } │ │234 if (data.forks_next_start) |next_start| │ │235 try items.append(aa, .{ .text = "next →", .link = try std.fmt.allocPrint(aa, "a:/user/{s}/forks/start:{d}", .{ data.user.name, next_start }) }); │ │236 try list.setItems(allocator, items.items); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/User/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────╮ │13 │ │14 name: []const u8, │ │15 title: ui.Title, │ │16 -auth_tab: AuthTab, │ │16 │ │17 const Self = @This();│ │18 │ ╰─────────────────────────╯ ╭────────────────────────────────────────────────────╮ │20 return .{ │ │21 .name = name, │ │22 .title = try ui.Title.init(arena, name),│ │24 - .auth_tab = AuthTab.init(), │ │23 }; │ │24 } │ │25 │ ╰────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯