╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────╮ │29 │ │30 header: Header, │ │31 repo: evt.Repo.Record,│ │32 +handle: ui.RepoHandle,│ │33 files: Files, │ │34 changes: Changes, │ │35 refs: Refs, │ ╰──────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │59 // every repo route stores its identity as "owner/name" (or elides it in local mode) │ │60 const name_str = route.repoIdentity() orelse return error.UnexpectedRoute; │ │61 const repo_identity = ui.RoutablePage.RepoIdentity.parse(name_str) orelse return error.NotFound;│ │61 - const location = ui.RoutablePage.RepoLocation{ .repo = repo_identity.identity }; │ │62 // files, commits and diff share the requested ref, or the default branch. │ │63 // directories and hunk windows only apply to their own tab. │ │64 const requested_ref_or_oid: ?ui.RoutablePage.RefOrOid = switch (route) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │264 }; │ │265 } │ │266 │ │267 - const viewer = try Comment.Viewer.init(session, arena, repo); │ │267 + const handle = ui.RepoHandle{ │ │268 + .location = .{ .repo = try arena.allocator().dupe(u8, repo_identity.identity) },│ │269 + .viewer = try ui.Viewer.init(session, arena, repo), │ │270 + }; │ │271 │ │272 // open the repo once for every tab. files and changes share a ref or revision. │ │273 // no filesystem (wasm), nowhere to look, or a failed open: empty tabs. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │271 - const undo_allowed = if (session.local) |local| local.repo_kind == .xit else session.userId() != null and evt.Repo.roleOf(repo, session.userId()).atLeast(.write);│ │272 - if (route == .repo_undo and !undo_allowed) return error.NotFound; │ │273 - // a server shows events to whoever it shows undo to, so the route follows │ │274 - if (route == .repo_events and session.data.host_kind != .local and !undo_allowed) return error.NotFound; │ │274 + // the events tab needs write access, like undo │ │275 + if (route == .repo_events and !handle.canWrite()) return error.NotFound; │ │276 const undo_index = if (route == .repo_undo) route.repo_undo.index else null; │ │277 const undo_clear = route == .repo_undo and route.repo_undo.clear; │ │277 - // an unreadable repo still shows the tab, like every other tab │ │278 - var undo_data: ?Undo = if (undo_allowed) .{ .identity = repo_identity.identity } else null; │ │278 + // only xit repos have undo history, so it's read in their branch below │ │279 + var undo_data: ?Undo = null; │ │280 const files, const changes, const refs, var issues, var patches, var discussions, const events = blk: { │ │281 read: { │ │282 const io = session.io orelse break :read; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │294 try pch.refreshBranches(.local, repo_kind, opened.self_repo_opts, io, gpa, opened, null, null); │ │295 } │ │296 // tabs switch in-page, so every tab's data is read here │ │296 - if (repo_kind == .xit and undo_allowed) { │ │297 - undo_data = Undo.init(opened.self_repo_opts, arena, opened, session.haxy_moment, repo_identity.identity, undo_index) catch |err| switch (err) { │ │297 + if (repo_kind == .xit and handle.canWrite()) { │ │298 + undo_data = Undo.init(opened.self_repo_opts, arena, opened, session.haxy_moment, repo_identity.identity, undo_index, handle) catch |err| switch (err) {│ │299 error.OutOfMemory => return err, │ │299 - else => .{ .identity = repo_identity.identity, .failure = @errorName(err) }, │ │300 + else => .{ .identity = repo_identity.identity, .handle = handle, .failure = @errorName(err) }, │ │301 }; │ │302 } │ │303 // a merge: route stands for the log of what the merge brought in │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │308 break :merged .{ .object, merged.oid, merged.base_oid }; │ │309 }; │ │310 const files_data = if (patchrev_id.len != 0) │ │310 - try Files.initPatchRev(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, location, patchrev_id, files_dir, files_line) │ │311 + try Files.initPatchRev(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, handle, patchrev_id, files_dir, files_line) │ │312 else │ │312 - try Files.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, location, ref_or_oid, ref_value, files_dir, files_line, files_find);│ │313 + try Files.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, handle, ref_or_oid, ref_value, files_dir, files_line, files_find); │ │314 const changes_data: Changes = if (route == .repo_diff) │ │315 .{ .diff = try Diff.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, route.repo_diff) } │ │316 else if (patchrev_id.len != 0) diff: { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │317 const diff_route = ui.RoutablePage.repoPatchRevDiffRoute(repo_identity.identity, patchrev_id, 0, "") orelse return error.RouteTooLong; │ │318 break :diff .{ .diff = try Diff.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, diff_route.repo_diff) }; │ │318 - } else .{ .commits = try Commits.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, session.haxy_moment, location, ref_or_oid, ref_value, commits_content, base_oid, commits_search, commits_from) };│ │319 + } else .{ .commits = try Commits.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, session.haxy_moment, handle, ref_or_oid, ref_value, commits_content, base_oid, commits_search, commits_from) }; │ │320 const target_branch = if (files_data.ref_or_oid == .branch) files_data.ref_or_oid_value else ""; │ │321 break :blk .{ │ │322 files_data, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │323 changes_data, │ │324 try Refs.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, repo_identity.identity, refs_kind, refs_from, refs_search), │ │324 - try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, issues_label, issues_search, issues_selected, issues_comment, issues_comments_start, issues_theirs, issues_view, viewer), │ │325 - try Patches.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, session, repo_id_maybe, repo_identity.identity, target_branch, patches_label, patches_search, patches_selected, patches_comment, patches_comments_start, patches_theirs, patches_view, viewer), │ │326 - try Discussions.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, discussions_label, discussions_search, discussions_selected, discussions_comment, discussions_comments_start, discussions_view, viewer), │ │325 + try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, issues_label, issues_search, issues_selected, issues_comment, issues_comments_start, issues_theirs, issues_view, handle.viewer), │ │326 + try Patches.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, session, repo_id_maybe, repo_identity.identity, target_branch, patches_label, patches_search, patches_selected, patches_comment, patches_comments_start, patches_theirs, patches_view, handle.viewer),│ │327 + try Discussions.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, discussions_label, discussions_search, discussions_selected, discussions_comment, discussions_comments_start, discussions_view, handle.viewer), │ │328 try Events.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, events_view, events_kind, events_selected, events_moment, session.local != null, session.data.sync_failure), │ │329 }; │ │330 }, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │334 } │ │335 const aa = arena.allocator(); │ │336 break :blk .{ │ │336 - try Files.emptyResult(aa, location, requested_ref_or_oid orelse .branch, requested_ref_value, files_dir), │ │337 - Changes{ .commits = try Commits.emptyResult(aa, location, requested_ref_or_oid orelse .branch, requested_ref_value, commits_content, commits_base_oid) }, │ │337 + try Files.emptyResult(aa, handle, requested_ref_or_oid orelse .branch, requested_ref_value, files_dir), │ │338 + Changes{ .commits = try Commits.emptyResult(aa, handle, requested_ref_or_oid orelse .branch, requested_ref_value, commits_content, commits_base_oid) }, │ │339 try Refs.emptyResult(arena, repo_identity.identity, refs_kind, refs_from, refs_search), │ │340 try Issues.emptyResult(aa, repo_identity.identity, issues_label, issues_search, issues_selected, issues_comment, issues_comments_start, issues_theirs, issues_view), │ │341 try Patches.emptyResult(aa, repo_identity.identity, patches_label, patches_search, patches_selected, patches_comment, patches_comments_start, patches_theirs, patches_view),│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │343 try Events.empty(aa, repo_identity.identity, events_view, session.local != null, session.data.sync_failure),│ │344 }; │ │345 }; │ │345 - if (undo_data) |*undo| { │ │346 - undo.can_undo = session.local != null or evt.Repo.roleOf(repo, session.userId()) == .owner; │ │347 - undo.clear = undo_clear; │ │348 - } │ │346 if (route == .repo_undo and undo_data == null) return error.NotFound; │ │347 + if (undo_data) |*undo| undo.clear = undo_clear; │ │348 issues.repo_source = source; │ │349 patches.repo_source = source; │ │350 patches.repo_id = repo_id_maybe; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯