╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1484 const tab = segments.next() orelse return null; │ │1485 var params = Params{}; │ │1486 if (std.mem.eql(u8, tab, "settings")) return if (segments.next() == null) .{ .repo_settings = Array(repo_route_max_len).from(pair) orelse return null } else null;│ │1487 + if (std.mem.eql(u8, tab, "new-repo")) return if (segments.next() == null) .{ .repo_new_repo = Array(repo_route_max_len).from(pair) orelse return null } else null;│ │1488 if (std.mem.eql(u8, tab, "auth")) return if (segments.next() == null) .{ .repo_auth = Array(repo_route_max_len).from(pair) orelse return null } else null; │ │1489 if (std.mem.eql(u8, tab, "refs")) { │ │1490 const word = params.scanTail(&segments) catch return null; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1741 .user_repos => |a_u| std.mem.eql(u8, a_u.name.slice(), b.user_repos.name.slice()) and a_u.start == b.user_repos.start and std.mem.eql(u8, a_u.search.slice(), b.user_repos.search.slice()),│ │1742 .user_forks => |a_u| std.mem.eql(u8, a_u.name.slice(), b.user_forks.name.slice()) and a_u.start == b.user_forks.start, │ │1743 .user_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.user_settings.slice()), │ │1744 + .user_new_repo => |a_name| std.mem.eql(u8, a_name.slice(), b.user_new_repo.slice()), │ │1745 .user_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.user_auth.slice()), │ │1746 .repo_files => |a_f| std.mem.eql(u8, a_f.name.slice(), b.repo_files.name.slice()) and │ │1747 a_f.ref_kind == b.repo_files.ref_kind and │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1803 a_e.kind == b.repo_events.kind and │ │1804 std.mem.eql(u8, a_e.selected.slice(), b.repo_events.selected.slice()), │ │1805 .repo_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_settings.slice()), │ │1806 + .repo_new_repo => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_new_repo.slice()), │ │1807 .repo_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_auth.slice()), │ │1808 .fork_patch => |a_f| std.mem.eql(u8, a_f.name.slice(), b.fork_patch.name.slice()) and std.mem.eql(u8, a_f.id.slice(), b.fork_patch.id.slice()),│ │1809 .fork_diff => |a_d| std.mem.eql(u8, a_d.fork.name.slice(), b.fork_diff.fork.name.slice()) and │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1826 .message => std.meta.activeTag(b.fork_commits.content) == .message, │ │1827 }, │ │1828 .fork_settings => |a_f| std.mem.eql(u8, a_f.name.slice(), b.fork_settings.name.slice()) and std.mem.eql(u8, a_f.id.slice(), b.fork_settings.id.slice()),│ │1829 + .fork_new_repo => |a_f| std.mem.eql(u8, a_f.name.slice(), b.fork_new_repo.name.slice()) and std.mem.eql(u8, a_f.id.slice(), b.fork_new_repo.id.slice()),│ │1830 .fork_auth => |a_f| std.mem.eql(u8, a_f.name.slice(), b.fork_auth.name.slice()) and std.mem.eql(u8, a_f.id.slice(), b.fork_auth.id.slice()), │ │1831 else => true, │ │1832 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │2158 error.InvalidSourceBranch => .invalid_source_branch, │ │2159 error.SameBranch => .same_branch, │ │2160 error.UnrelatedBranches => .unrelated_branches, │ │2161 + else => null, │ │2162 + }; │ │2163 + } │ │2164 + }; │ │2165 + │ │2166 + pub const RepoFailure = enum { │ │2167 + required_name, │ │2168 + invalid_name, │ │2169 + name_taken, │ │2170 + │ │2171 + pub fn fromError(err: anyerror) ?RepoFailure { │ │2172 + return switch (err) { │ │2173 + error.NameEmpty => .required_name, │ │2174 + error.NameTooLong, error.InvalidName => .invalid_name,│ │2175 + error.NameTaken => .name_taken, │ │2176 else => null, │ │2177 }; │ │2178 } │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────╮ │2181 login: struct { │ │2182 failure: LoginFailure, │ │2183 username: []const u8, │ │2184 + }, │ │2185 + repo: struct { │ │2186 + failure: RepoFailure, │ │2187 + fields: struct { │ │2188 + name: []const u8, │ │2189 + description: []const u8,│ │2190 + hash: hash.HashKind, │ │2191 + access: evt.Repo.Access,│ │2192 + }, │ │2193 }, │ │2194 issue: struct { │ │2195 failure: ThreadFailure, │ ╰──────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Fork.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 59 const target_identity = try std.fmt.allocPrint(aa, "{s}/{s}", .{ owner.event.name, target_record.event.name }); │ │ 60 const id_hex = std.fmt.bytesToHex(id, .lower); │ │ 61 const fork_path = try fork.forkPath(aa, users_dir, &forker_id, &id); │ │ 62 - var fork_repo = try rp.Repo(.xit, .{}).open(io, arena.child_allocator, .{ .path = fork_path, .require_repo_root = true }); │ │ 63 - defer fork_repo.deinit(io, arena.child_allocator); │ │ 64 - const fork_moment = try evt.currentMoment(.{}, &fork_repo); │ │ 65 - const retained_patch = (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, fork_moment, arena, &id)) orelse return error.NotFound; │ │ 66 - const fork_oid = (try fork_repo.readRef(io, fork.ref)) orelse return error.NotFound; │ │ 67 - const newest_revision = try evt.PatchRev.readNewest(evt.EventDB(.sha1), .sha1, fork_moment, arena); │ │ 68 - var commits_base_oid = fork_oid; │ │ 69 - if (newest_revision) |revision| { │ │ 70 - if (revision.record.event.base_oid.len != commits_base_oid.len) return error.NotFound; │ │ 71 - @memcpy(&commits_base_oid, revision.record.event.base_oid); │ │ 72 - } │ │ 62 + // a fork shares its target's hash kind │ │ 63 + var any_fork = try rp.AnyRepo(.xit, .{}).open(io, arena.child_allocator, .{ .path = fork_path, .require_repo_root = true }); │ │ 64 + defer any_fork.deinit(io, arena.child_allocator); │ │ 65 + switch (any_fork) { │ │ 66 + inline else => |*fork_repo| { │ │ 67 + const repo_opts = fork_repo.self_repo_opts; │ │ 68 + const fork_moment = try evt.currentMoment(repo_opts, fork_repo); │ │ 69 + const retained_patch = (try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, arena, &id)) orelse return error.NotFound; │ │ 70 + const fork_oid = (try fork_repo.readRef(io, fork.ref)) orelse return error.NotFound; │ │ 71 + const newest_revision = try evt.PatchRev.readNewest(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, arena); │ │ 72 + var commits_base_oid = fork_oid; │ │ 73 + if (newest_revision) |revision| { │ │ 74 + if (revision.record.event.base_oid.len != commits_base_oid.len) return error.NotFound; │ │ 75 + @memcpy(&commits_base_oid, revision.record.event.base_oid); │ │ 76 + } │ │ 77 │ │ 74 - const target_path = try evt.repoPath(aa, users_dir, owner_id, &target_id); │ │ 75 - const target_source = ui.RepoSource{ .path = target_path, .repo_kind = .xit }; │ │ 76 - var target_repo_maybe: ?rp.Repo(.xit, .{}) = if (!target_record.removed) │ │ 77 - rp.Repo(.xit, .{}).open(io, arena.child_allocator, target_source.localInitOpts()) catch null │ │ 78 - else │ │ 79 - null; │ │ 80 - defer if (target_repo_maybe) |*target_repo| target_repo.deinit(io, arena.child_allocator); │ │ 78 + const target_path = try evt.repoPath(aa, users_dir, owner_id, &target_id); │ │ 79 + const target_source = ui.RepoSource{ .path = target_path, .repo_kind = .xit }; │ │ 80 + var target_repo_maybe: ?rp.Repo(.xit, repo_opts) = if (!target_record.removed) │ │ 81 + rp.Repo(.xit, repo_opts).open(io, arena.child_allocator, target_source.localInitOpts()) catch null │ │ 82 + else │ │ 83 + null; │ │ 84 + defer if (target_repo_maybe) |*target_repo| target_repo.deinit(io, arena.child_allocator); │ │ 85 │ │ 82 - const target_branch = retained_patch.event.target_branch; │ │ 86 + const target_branch = retained_patch.event.target_branch; │ │ 87 │ │ 84 - const retained_entry = Patches.PatchWithId{ │ │ 85 - .id = try aa.dupe(u8, &id_hex), │ │ 86 - .record = retained_patch, │ │ 87 - .author = try ui.Author.initFromEmail(haxy_moment, arena, retained_patch.author_email), │ │ 88 - .draft = fork_record.event.stage == .draft, │ │ 89 - .revision_oid = try aa.dupe(u8, &fork_oid), │ │ 90 - .fork_exists = true, │ │ 91 - .forker = identity.owner, │ │ 92 - }; │ │ 93 - var patch_data = try Patches.detailResult(aa, target_identity, retained_entry); │ │ 88 + const retained_entry = Patches.PatchWithId{ │ │ 89 + .id = try aa.dupe(u8, &id_hex), │ │ 90 + .record = retained_patch, │ │ 91 + .author = try ui.Author.initFromEmail(haxy_moment, arena, retained_patch.author_email), │ │ 92 + .draft = fork_record.event.stage == .draft, │ │ 93 + .revision_oid = try aa.dupe(u8, &fork_oid), │ │ 94 + .fork_exists = true, │ │ 95 + .forker = identity.owner, │ │ 96 + }; │ │ 97 + var patch_data = try Patches.detailResult(aa, target_identity, retained_entry); │ │ 98 │ │ 95 - if (target_repo_maybe) |*target_repo| switch (fork_record.event.stage) { │ │ 96 - .draft => if (try Patches.loadDraftEntry(.xit, .{}, arena, io, haxy_moment, &fork_repo, target_repo, id, identity.owner)) |entry| { │ │ 97 - patch_data = try Patches.detailResult(aa, target_identity, entry); │ │ 98 - patch_data.repo_source = target_source; │ │ 99 - }, │ │100 - .publish => { │ │101 - patch_data = Patches.init(.xit, .{}, arena, target_repo, io, haxy_moment, session, target_id, target_identity, target_branch, "", "", &id_hex, "", 0, "", .open) catch |err| switch (err) { │ │102 - error.NotFound => patch_data, │ │103 - else => |other| return other, │ │ 99 + if (target_repo_maybe) |*target_repo| switch (fork_record.event.stage) { │ │100 + .draft => if (try Patches.loadDraftEntry(.xit, repo_opts, arena, io, haxy_moment, fork_repo, target_repo, id, identity.owner)) |entry| { │ │101 + patch_data = try Patches.detailResult(aa, target_identity, entry); │ │102 + patch_data.repo_source = target_source; │ │103 + }, │ │104 + .publish => { │ │105 + patch_data = Patches.init(.xit, repo_opts, arena, target_repo, io, haxy_moment, session, target_id, target_identity, target_branch, "", "", &id_hex, "", 0, "", .open) catch |err| switch (err) {│ │106 + error.NotFound => patch_data, │ │107 + else => |other| return other, │ │108 + }; │ │109 + if (patch_data.selectedThread() != null) patch_data.repo_source = target_source; │ │110 + }, │ │111 }; │ │105 - if (patch_data.selectedThread() != null) patch_data.repo_source = target_source; │ │106 - }, │ │107 - }; │ │108 - patch_data.repo_id = target_id; │ │112 + patch_data.repo_id = target_id; │ │113 │ │110 - const requested_ref: ?ui.RoutablePage.RefOrOid = switch (route) { │ │111 - .fork_files => |f| if (f.oid.len == 0) null else .object, │ │112 - .fork_commits => |c| if (c.oid.len == 0) null else .object, │ │113 - else => null, │ │114 - }; │ │115 - const requested_value: []const u8 = switch (route) { │ │116 - .fork_files => |*f| f.oid.slice(), │ │117 - .fork_commits => |*c| c.oid.slice(), │ │118 - else => "", │ │119 - }; │ │120 - const files_path: []const u8 = switch (route) { │ │121 - .fork_files => |*f| f.path.slice(), │ │122 - else => "", │ │123 - }; │ │124 - const files_line: usize = switch (route) { │ │125 - .fork_files => |f| f.line, │ │126 - else => 0, │ │127 - }; │ │128 - const files_find: []const u8 = switch (route) { │ │129 - .fork_files => |*f| f.find.slice(), │ │130 - else => "", │ │131 - }; │ │132 - const commits_content: ui.RoutablePage.RepoCommitsRoute.Content = switch (route) { │ │133 - .fork_commits => |c| c.content, │ │134 - else => .{ .diff = .{} }, │ │135 - }; │ │136 - const location = ui.RoutablePage.RepoLocation{ .fork = .{ │ │137 - .identity = identity.identity, │ │138 - .id = &id_hex, │ │139 - } }; │ │140 - const files = try Files.init(.xit, .{}, arena, &fork_repo, io, arena.child_allocator, location, requested_ref, requested_value, files_path, files_line, files_find); │ │141 - var commits = try Commits.init(.xit, .{}, arena, &fork_repo, io, arena.child_allocator, haxy_moment, location, requested_ref, requested_value, commits_content, &commits_base_oid, "", ""); │ │142 - commits.commit_count = if (newest_revision) |revision| revision.record.commit_count else 0; │ │143 - const diff_start: usize = switch (route) { │ │144 - .fork_diff => |d| d.start, │ │145 - else => 0, │ │146 - }; │ │147 - const diff_path = switch (route) { │ │148 - .fork_diff => |*d| try aa.dupe(u8, d.path.slice()), │ │149 - else => "", │ │150 - }; │ │114 + const requested_ref: ?ui.RoutablePage.RefOrOid = switch (route) { │ │115 + .fork_files => |f| if (f.oid.len == 0) null else .object, │ │116 + .fork_commits => |c| if (c.oid.len == 0) null else .object, │ │117 + else => null, │ │118 + }; │ │119 + const requested_value: []const u8 = switch (route) { │ │120 + .fork_files => |*f| f.oid.slice(), │ │121 + .fork_commits => |*c| c.oid.slice(), │ │122 + else => "", │ │123 + }; │ │124 + const files_path: []const u8 = switch (route) { │ │125 + .fork_files => |*f| f.path.slice(), │ │126 + else => "", │ │127 + }; │ │128 + const files_line: usize = switch (route) { │ │129 + .fork_files => |f| f.line, │ │130 + else => 0, │ │131 + }; │ │132 + const files_find: []const u8 = switch (route) { │ │133 + .fork_files => |*f| f.find.slice(), │ │134 + else => "", │ │135 + }; │ │136 + const commits_content: ui.RoutablePage.RepoCommitsRoute.Content = switch (route) { │ │137 + .fork_commits => |c| c.content, │ │138 + else => .{ .diff = .{} }, │ │139 + }; │ │140 + const location = ui.RoutablePage.RepoLocation{ .fork = .{ │ │141 + .identity = identity.identity, │ │142 + .id = &id_hex, │ │143 + } }; │ │144 + 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); │ │145 + 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, "", ""); │ │146 + commits.commit_count = if (newest_revision) |revision| revision.record.commit_count else 0; │ │147 + const diff_start: usize = switch (route) { │ │148 + .fork_diff => |d| d.start, │ │149 + else => 0, │ │150 + }; │ │151 + const diff_path = switch (route) { │ │152 + .fork_diff => |*d| try aa.dupe(u8, d.path.slice()), │ │153 + else => "", │ │154 + }; │ │155 │ │152 - return .{ │ │153 - .header = try Header.init(arena, target_record.event.name, identity.owner, &id_hex, requested_value), │ │154 - .files = files, │ │155 - .commits = commits, │ │156 - .patch = patch_data, │ │157 - .diff = .{ │ │158 - .route = .{ .fork = .{ .identity = try aa.dupe(u8, identity.identity), .id = try aa.dupe(u8, &id_hex) } }, │ │159 - .path = diff_path, │ │160 - .window = try Diff.render(.xit, .{}, io, arena.child_allocator, aa, &fork_repo, &commits_base_oid, fork_oid, diff_start, diff_path), │ │156 + return .{ │ │157 + .header = try Header.init(arena, target_record.event.name, identity.owner, &id_hex, requested_value), │ │158 + .files = files, │ │159 + .commits = commits, │ │160 + .patch = patch_data, │ │161 + .diff = .{ │ │162 + .route = .{ .fork = .{ .identity = try aa.dupe(u8, identity.identity), .id = try aa.dupe(u8, &id_hex) } }, │ │163 + .path = diff_path, │ │164 + .window = try Diff.render(.xit, repo_opts, io, arena.child_allocator, aa, fork_repo, &commits_base_oid, fork_oid, diff_start, diff_path), │ │165 + }, │ │166 + .settings = Settings.init(), │ │167 + .auth = Auth.init(), │ │168 + .quit = Quit.init(), │ │169 + }; │ │170 }, │ │162 - .settings = Settings.init(), │ │163 - .auth = Auth.init(), │ │164 - .quit = Quit.init(), │ │165 - }; │ │171 + } │ │172 } │ │173 │ │174 pub const View = struct { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │206 var commits = try Commits.View.init(allocator, &data.commits, session); │ │207 errdefer commits.deinit(allocator); │ │208 try stack.children.put(allocator, commits.getFocus().id, .{ .repo_commits = commits }); │ │209 + } │ │210 + if (session.data.user_id != null) { │ │211 + const identity = try std.fmt.allocPrint(session.page_arena.allocator(), "{s}/{s}", .{ data.header.forker_name, data.header.name });│ │212 + const route = ui.RoutablePage.forkNewRepoRoute(identity, data.header.id) orelse return error.RouteTooLong; │ │213 + var new_repo = try ui.NewRepo.View.init(allocator, session, route); │ │214 + errdefer new_repo.deinit(allocator); │ │215 + try stack.children.put(allocator, new_repo.getFocus().id, .{ .new_repo = new_repo }); │ │216 } │ │217 if (session.data.user_id != null) { │ │218 var settings = try Settings.View.init(allocator, session); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Fork/Header.zig│ ╰──────────────────────╯ ╭──────────────────────────────────────────────────╮ │15 const diff_tab_label = "diff"; │ │16 const files_tab_label = "files"; │ │17 const settings_tab_label = "settings"; │ │18 +const new_repo_tab_label = "new repo"; │ │19 │ │20 pub const AuthTab = @import("../AuthTab.zig");│ │21 │ ╰──────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │122 }); │ │123 } │ │124 │ │124 - // spacer pushes settings + auth to the right. │ │125 + // spacer pushes settings, new repo and auth to the right.│ │126 { │ │127 var spacer = try ui.widget.Spacer.init(allocator); │ │128 errdefer spacer.deinit(allocator); │ ╰───────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯