╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │934 const drafts_tab_label = "drafts"; │ │935 const conflicts_tab_label = "conflicts"; │ │936 │ │938 -pub fn appendDetails(self: *const Self, allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), session: *ui.Session, entry: Entry) !void {│ │937 +pub fn appendDetails(_: *const Self, allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), session: *ui.Session, entry: Entry) !void { │ │938 const aa = session.page_arena.allocator(); │ │939 const status_kind = entry.record.event.status.kind(); │ │940 const target_branch = entry.record.event.target_branch; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │941 │ │942 if (status_kind != .merged and entry.fork_exists) if (session.data.git_ssh_port) |port| { │ │944 - var buffer: [ui.RoutablePage.repo_identity_max_len]u8 = undefined; │ │945 - const url = try aa.print("ssh://localhost:{d}/{s}+{s}", .{ port, try forkIdentity(&buffer, self.identity, entry), entry.id });│ │943 + const url = try aa.print("ssh://localhost:{d}/{s}+{s}", .{ port, entry.fork_identity, entry.id }); │ │944 const push_command = try aa.print("git push {s} HEAD:patch", .{url}); │ │945 const clone_command = try aa.print("git clone {s}", .{url}); │ │946 const merge_command = try aa.print("git fetch '{s}' refs/heads/patch && git merge FETCH_HEAD", .{url}); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────╮ │949 - const choices: [3]ui.widget.CopyableText.Choice = .{│ │947 + const choices = [_]ui.widget.CopyableText.Choice{ │ │948 .{ │ │949 .selector = "push", │ │950 .text = push_command, │ ╰─────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │964 .label = " merge this patch locally ", │ │965 }, │ │966 }; │ │969 - var copyable_text = try ui.widget.CopyableText.init(allocator, session, &choices, 0); │ │967 + var copyable_text = try ui.widget.CopyableText.init(allocator, session, if (entry.target_deleted) choices[1..] else &choices, 0); │ │968 errdefer copyable_text.deinit(allocator); │ │969 try box.children.put(allocator, copyable_text.getFocus().id, .{ .widget = .{ .copyable_text = copyable_text }, .rect = null, .min_size = null });│ │970 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/ui/Repo/Settings.zig│ ╰────────────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │30 patch_role: ?evt.Repo.Role, │ │31 hash_kind: hash.HashKind, │ │32 // the sub tab the route names │ │33 -view: enum { settings, members, branches }, │ │33 +view: enum { settings, members, branches, delete }, │ │34 members: Members, │ │35 branches: Branches, │ │36 +// the delete sub tab's form, for the repo's creator only│ │37 +delete: ?ui.Delete, │ │38 │ │39 const Self = @This(); │ │40 │ ╰─────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │64 return error.InvalidRole; │ │65 } │ │66 │ │65 -// the repo settings, members and branches sub tabs over their content │ │67 +// the repo settings, members, branches and delete sub tabs over their content│ │68 pub const View = struct { │ │69 box: wgt.Box(ui.Widget), │ │70 │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │80 errdefer header_scroll.deinit(allocator); │ │81 const header = header_scroll.row(); │ │82 const on_tab = switch (session.data.current_page) { │ │81 - .repo_settings, .repo_members, .repo_branches => true, │ │83 + .repo_settings, .repo_members, .repo_branches, .repo_delete => true, │ │84 else => false, │ │85 }; │ │84 - const routes = [_]?ui.RoutablePage{ ui.RoutablePage.repoSettingsRoute(data.identity), ui.RoutablePage.repoMembersRoute(data.identity, 0), ui.RoutablePage.repoBranchesRoute(data.identity) }; │ │85 - for ([_]@FieldType(Self, "view"){ .settings, .members, .branches }, [_][]const u8{ "repo settings", "members", "branches" }, routes) |view, label, route| { │ │86 + const routes = [_]?ui.RoutablePage{ ui.RoutablePage.repoSettingsRoute(data.identity), ui.RoutablePage.repoMembersRoute(data.identity, 0), ui.RoutablePage.repoBranchesRoute(data.identity), ui.RoutablePage.repoDeleteRoute(data.identity) };│ │87 + const views = [_]@FieldType(Self, "view"){ .settings, .members, .branches, .delete }; │ │88 + const labels = [_][]const u8{ "repo settings", "members", "branches", "delete repo" }; │ │89 + // the delete tab is last, so leaving it out keeps the stack's order │ │90 + const tab_count = if (data.delete != null) views.len else views.len - 1; │ │91 + for (views[0..tab_count], labels[0..tab_count], routes[0..tab_count]) |view, label, route| { │ │92 var tab = try wgt.TextBox.init(allocator, label, .{ .border = .hidden, .round_corners = true, .wrap_kind = .none }); │ │93 errdefer tab.deinit(allocator); │ │94 tab.getFocus().mode = .all; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │118 errdefer branches.deinit(allocator); │ │119 try stack.children.put(allocator, branches.getFocus().id, .{ .repo_branches = branches }); │ │120 } │ │121 + if (data.delete) |delete| { │ │122 + var view = try ui.Delete.View.init(allocator, delete, session, ui.RoutablePage.repoDeleteRoute(data.identity) orelse return error.RouteTooLong);│ │123 + errdefer view.deinit(allocator); │ │124 + try stack.children.put(allocator, view.getFocus().id, .{ .delete_form = view }); │ │125 + } │ │126 stack.getFocus().child_id = stack.children.keys()[@backingInt(data.view)]; │ │127 try box.children.put(allocator, stack.getFocus().id, .{ .widget = .{ .stack = stack }, .rect = null, .min_size = null }); │ │128 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/User.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │20 │ │21 pub const ForkItem = struct { │ │22 id: []const u8, │ │23 - // the target repo, whose name and access the fork shares │ │24 - target: evt.Repo, │ │23 + // the repo name the fork was made under │ │24 + repo_name: []const u8, │ │25 + // the target's access, which the fork shares, or null once the target is gone│ │26 + access: ?evt.Repo.Access, │ │27 title: []const u8, │ │28 }; │ │29 │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │137 │ │138 // the target lives in its owner's user repo │ │139 const owner_id = try evt.parseEventId(&record.event.repo_user_id); │ │138 - const target = (try evt.readRepoById(io, gpa, arena, users_dir, &owner_id, &try evt.parseEventId(&record.event.repo_id), session.userId())) orelse continue;│ │139 - if (target.role == null) continue; │ │140 - // patches that are off take their drafts with them │ │141 - if (target.repo.event.patch_role == null) continue; │ │142 - const target_repo = target.repo; │ │140 + // a deleted target, or one whose owner was deleted, leaves the fork to its forker │ │141 + const target = try evt.readRepoById(io, gpa, arena, users_dir, &owner_id, &try evt.parseEventId(&record.event.repo_id), session.userId()); │ │142 + if (target) |value| { │ │143 + if (value.role == null) continue; │ │144 + // patches that are off take their drafts with them │ │145 + if (value.repo.event.patch_role == null) continue; │ │146 + } else { │ │147 + const viewer_id = session.userId() orelse continue; │ │148 + if (!std.mem.eql(u8, &viewer_id, &user_id)) continue; │ │149 + } │ │150 │ │151 var title: []const u8 = "(unavailable)"; │ │152 const path = try fork.forkPath(arena.allocator(), users_dir, &user_id, &fork_id); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯