╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/test/undo.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │40 try expectConfig(&repo, "23"); │ │41 try repo.undo(io, 10); │ │42 try expectConfig(&repo, "8"); │ │43 - const undone = try Undo.init(opts, &arena, &repo, null, "", null); │ │43 + const undone = try Undo.init(opts, &arena, &repo, null, "", null, .{ .location = .{ .repo = "" } });│ │44 try std.testing.expectEqual(28, undone.count); │ │45 try std.testing.expectEqualStrings("undo", undone.items[0].action); │ │46 try repo.undo(io, undone.items[0].index); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 96 session.data.current_page = files; │ │ 97 const page = try ui.Repo.init(&arena, &session, files); │ │ 98 try std.testing.expectEqual(allowed, page.undo != null); │ │ 99 - if (page.undo) |undo| try std.testing.expectEqual(user != null and std.mem.eql(u8, user.?, &owner), undo.can_undo); │ │ 99 + if (page.undo) |undo| try std.testing.expectEqual(user != null and std.mem.eql(u8, user.?, &owner), undo.handle.canUndo());│ │100 var root = ui.Widget{ .repo = try ui.Repo.View.init(allocator, &page, &session) }; │ │101 defer root.deinit(allocator); │ │102 try root.build(allocator, constraint, root.getFocus()); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │122 var repo = try xit.repo.Repo(.xit, .{}).open(io, allocator, .{ .path = path }); │ │123 defer repo.deinit(io, allocator); │ │124 try expectConfig(&repo, "first"); │ │125 - const data = try Undo.init(.{}, &arena, &repo, null, "alice/demo", null); │ │125 + const data = try Undo.init(.{}, &arena, &repo, null, "alice/demo", null, .{ .location = .{ .repo = "" } });│ │126 try std.testing.expectEqualStrings("undo", data.items[0].action); │ │127 } │ │128 // keep the old session/page moment, but transfer ownership to the writer. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │184 var repo = try xit.repo.Repo(.xit, opts).init(io, allocator, .{ .path = path }); │ │185 defer repo.deinit(io, allocator); │ │186 _ = try repo.commitAtRef(io, allocator, .{ .message = "example commit" }, null, .{ .kind = .head, .name = "master" });│ │187 - const data = try Undo.init(opts, &arena, &repo, null, "", null); │ │187 + const data = try Undo.init(opts, &arena, &repo, null, "", null, .{ .location = .{ .repo = "" } }); │ │188 try std.testing.expect(std.mem.startsWith(u8, data.items[0].description, "commit -m \"example commit\"")); │ │189 } │ │190 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1946 } │ │1947 }; │ │1948 } │ │1949 + │ │1950 +// who a repo page is built for, which picks the actions it shows │ │1951 +pub const Viewer = struct { │ │1952 + role: evt.Repo.Role, │ │1953 + email: []const u8, │ │1954 + │ │1955 + // the session's viewer of `repo`, or null when logged out. local mode owns everything. │ │1956 + pub fn init(session: *Session, arena: *std.heap.ArenaAllocator, repo: evt.Repo.Record) !?Viewer {│ │1957 + if (session.data.host_kind == .local) return .{ .role = .owner, .email = "" }; │ │1958 + const user_id = session.userId() orelse return null; │ │1959 + const moment = session.haxy_moment orelse unreachable; │ │1960 + const user = (try activeUser(moment, arena, user_id)) orelse return null; │ │1961 + return .{ .role = evt.Repo.roleOf(repo, user_id), .email = user.event.email }; │ │1962 + } │ │1963 +}; │ │1964 + │ │1965 +// a repo a page shows, and what its viewer may do there │ │1966 +pub const RepoHandle = struct { │ │1967 + location: RoutablePage.RepoLocation, │ │1968 + viewer: ?Viewer = null, │ │1969 + │ │1970 + pub fn dupe(self: RepoHandle, allocator: std.mem.Allocator) !RepoHandle { │ │1971 + var copy = self; │ │1972 + copy.location = try self.location.dupe(allocator); │ │1973 + return copy; │ │1974 + } │ │1975 │ │1976 + // pushing, and seeing the events and undo tabs. local mode is always owner. │ │1977 + pub fn canWrite(self: RepoHandle) bool { │ │1978 + const viewer = self.viewer orelse return false; │ │1979 + return viewer.role.atLeast(.write); │ │1980 + } │ │1981 + │ │1982 + pub fn canUndo(self: RepoHandle) bool { │ │1983 + const viewer = self.viewer orelse return false; │ │1984 + return viewer.role == .owner; │ │1985 + } │ │1986 +}; │ │1987 + │ │1988 // where a repo page reads its on-disk repo from │ │1989 pub const RepoSource = struct { │ │1990 path: []const u8, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Fork.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────╮ │19 pub const Patches = @import("./Repo/Patches.zig"); │ │20 pub const Auth = @import("./Auth.zig"); │ │21 pub const Quit = @import("./Quit.zig"); │ │22 -const Viewer = @import("./Repo/Comment.zig").Viewer;│ │22 │ │23 header: Header, │ │24 files: Files, │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │56 const aa = arena.allocator(); │ │57 const target_identity = try std.fmt.allocPrint(aa, "{s}/{s}", .{ owner.event.name, target_record.event.name }); │ │58 const id_hex = std.fmt.bytesToHex(id, .lower); │ │59 + // the fork's viewer is the target repo's │ │60 + const handle = ui.RepoHandle{ │ │61 + .location = .{ .fork = .{ .identity = identity.identity, .id = &id_hex } }, │ │62 + .viewer = try ui.Viewer.init(session, arena, target_record), │ │63 + }; │ │64 const fork_path = try fork.forkPath(aa, users_dir, &forker_id, &id); │ │65 // a fork shares its target's hash kind │ │66 var any_fork = try rp.AnyRepo(.xit, .{}).open(io, arena.child_allocator, .{ .path = fork_path, .require_repo_root = true });│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │105 patch_data.repo_source = target_source; │ │106 }, │ │107 .publish => { │ │104 - patch_data = Patches.init(.xit, repo_opts, arena, target_repo, io, haxy_moment, session, target_id, target_identity, target_branch, "", "", &id_hex, "", 0, "", .open, try Viewer.init(session, arena, target_record)) catch |err| switch (err) {│ │108 + patch_data = Patches.init(.xit, repo_opts, arena, target_repo, io, haxy_moment, session, target_id, target_identity, target_branch, "", "", &id_hex, "", 0, "", .open, handle.viewer) catch |err| switch (err) { │ │109 error.NotFound => patch_data, │ │110 else => |other| return other, │ │111 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │140 .fork_commits => |c| c.content, │ │141 else => .{ .diff = .{} }, │ │142 }; │ │139 - const location = ui.RoutablePage.RepoLocation{ .fork = .{ │ │140 - .identity = identity.identity, │ │141 - .id = &id_hex, │ │142 - } }; │ │143 - const files = try Files.init(.xit, repo_opts, arena, fork_repo, io, arena.child_allocator, location, requested_ref, requested_value, files_path, files_line, files_find); │ │144 - var commits = try Commits.init(.xit, repo_opts, arena, fork_repo, io, arena.child_allocator, haxy_moment, location, requested_ref, requested_value, commits_content, &commits_base_oid, "", "");│ │143 + const files = try Files.init(.xit, repo_opts, arena, fork_repo, io, arena.child_allocator, handle, requested_ref, requested_value, files_path, files_line, files_find); │ │144 + var commits = try Commits.init(.xit, repo_opts, arena, fork_repo, io, arena.child_allocator, haxy_moment, handle, requested_ref, requested_value, commits_content, &commits_base_oid, "", ""); │ │145 commits.commit_count = if (newest_revision) |revision| revision.record.commit_count else 0; │ │146 const diff_start: usize = switch (route) { │ │147 .fork_diff => |d| d.start, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭──────────────────────────╮ │29 │ │30 header: Header, │ │31 repo: evt.Repo.Record,│ │32 +handle: ui.RepoHandle,│ │33 files: Files, │ │34 changes: Changes, │ │35 refs: Refs, │ ╰──────────────────────────╯ ╭──────╮ │next →│ ╰──────╯