╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │46 // didn't name one), so the view's directory links stay pinned to it. │ │47 ref_or_oid: ui.RoutablePage.RefOrOid, │ │48 ref_or_oid_value: []const u8, │ │49 +patchrev_id: ?[]const u8 = null, │ │50 // the directory being viewed, relative to the repo root ("" at the root).│ │51 dir: []const u8, │ │52 // a file within `dir` to start selected (so its contents show and the url│ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │58 header: Header, │ │59 │ │60 const Self = @This(); │ │61 + │ │62 +pub fn initPatchRev( │ │63 + comptime repo_kind: rp.RepoKind, │ │64 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │65 + arena: *std.heap.ArenaAllocator, │ │66 + repo: *rp.Repo(repo_kind, repo_opts), │ │67 + io: std.Io, │ │68 + gpa: std.mem.Allocator, │ │69 + location: ui.RoutablePage.RepoLocation, │ │70 + id_hex: []const u8, │ │71 + path: []const u8, │ │72 + line: usize, │ │73 +) !Self { │ │74 + const id = evt.parseEventId(id_hex) catch return error.NotFound; │ │75 + const record = (try evt.readFromRepo(evt.PatchRev, repo_kind, repo_opts, io, gpa, arena, repo, &id)) orelse return error.NotFound;│ │76 + if (record.removed) return error.NotFound; │ │77 + var data = try init(repo_kind, repo_opts, arena, repo, io, gpa, location, .object, record.head_tree_oid, path, line); │ │78 + data.patchrev_id = try arena.allocator().dupe(u8, id_hex); │ │79 + data.header.content = try std.fmt.allocPrint(arena.allocator(), "patchrev {s}", .{id_hex}); │ │80 + return data; │ │81 +} │ │82 + │ │83 +pub fn filesRoute(self: *const Self, path: []const u8, line: usize) ?ui.RoutablePage { │ │84 + if (self.patchrev_id) |id| return switch (self.location) { │ │85 + .repo => |identity| ui.RoutablePage.repoPatchRevFilesRoute(identity, id, path, line), │ │86 + .fork => null, │ │87 + }; │ │88 + return self.location.filesRoute(self.ref_or_oid, self.ref_or_oid_value, path, line); │ │89 +} │ │90 │ │91 // build the listing for an opened repo. generic over the repo's backend and │ │92 // hash kind so the tree/diff types it threads through match the repo's opts. │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │107 ) !Self { │ │108 const aa = arena.allocator(); │ │109 │ │ 80 - // resolve the requested ref (or the default branch) to the commit oid whose │ │110 + // resolve the requested ref (or the default branch) to the object oid whose │ │111 // tree we list. a ref the route named explicitly that doesn't resolve is a │ │112 // bad url (NotFound -> 404); the default-branch path falls through to empty. │ │113 const resolved = (try ui.ResolvedRefOrOid(repo_kind, repo_opts).init(repo, io, aa, requested_ref_or_oid, requested_value)) orelse {│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │115 return emptyResult(aa, location, .branch, requested_value, path); │ │116 }; │ │117 │ │ 88 - // read just the viewed directory of that commit's tree. building the │ │118 + // read just the viewed directory of that tree or commit. building the │ │119 // read-only state mirrors what repo.status does internally, but for an │ │120 // arbitrary commit rather than HEAD. a path that doesn't exist in the tree│ │121 // is a bad url (404). │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │592 fn addNavLink(self: *View, allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), label: []const u8, entry: Entry, target_start: usize) !void { │ │593 const page_arena = self.session.page_arena; │ │594 const path = try childDir(page_arena.allocator(), self.data.dir, entry.name); │ │565 - const route = self.data.location.filesRoute(self.data.ref_or_oid, self.data.ref_or_oid_value, path, target_start) orelse return error.RouteTooLong;│ │595 + const route = self.data.filesRoute(path, target_start) orelse return error.RouteTooLong; │ │596 const link = try std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{try route.toUrl(page_arena)}); │ │597 var tb = try wgt.TextBox.init(allocator, label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │598 errdefer tb.deinit(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │834 // an "a:" link to the files route at `path`, pinned to the listing's ref, so │ │835 // following it navigates to that directory's listing. │ │836 fn dirLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, path: []const u8) ![]const u8 { │ │807 - const route = data.location.filesRoute(data.ref_or_oid, data.ref_or_oid_value, path, 0) orelse return error.RouteTooLong;│ │837 + const route = data.filesRoute(path, 0) orelse return error.RouteTooLong; │ │838 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{try route.toUrl(page_arena)}); │ │839 } │ │840 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │843 // contents); an unloaded file gets an "a:" link, so activating it reloads the │ │844 // page with that file selected and its content read. │ │845 fn fileLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, path: []const u8, loaded: bool, line: usize) ![]const u8 { │ │816 - const route = data.location.filesRoute(data.ref_or_oid, data.ref_or_oid_value, path, line) orelse return error.RouteTooLong;│ │846 + const route = data.filesRoute(path, line) orelse return error.RouteTooLong; │ │847 const prefix = if (loaded) "ai:" else "a:"; │ │848 return std.fmt.allocPrint(page_arena.allocator(), "{s}{s}", .{ prefix, try route.toUrl(page_arena) }); │ │849 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯