╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1701 if (!params.only(&.{.merge}) or segments.next() != null) return null; │ │1702 return repoCommitsMergeRoute(pair, oid); │ │1703 } │ │1715 - if (!params.only(&.{ .start, .branch, .tag, .object, .base, .search, .from })) return null;│ │1704 + if (!params.only(&.{ .branch, .tag, .object, .base, .search, .from })) return null; │ │1705 const base_oid = params.values.get(.base) orelse ""; │ │1706 const search = params.values.get(.search) orelse ""; │ │1707 const from = params.values.get(.from) orelse ""; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1719 - const start = params.start() orelse return null; │ │1708 const ref = (params.ref() catch return null) orelse { │ │1721 - if (search.len != 0 or from.len != 0) return null; │ │1722 - const file = pathValue(segments.rest()) orelse return null; │ │1723 - return if (file.len == 0) repoCommitsRoute(pair, null, "", start, "", base_oid) else null;│ │1709 + if (search.len != 0 or from.len != 0 or segments.rest().len != 0) return null; │ │1710 + return repoCommitsRoute(pair, null, "", base_oid); │ │1711 }; │ │1712 // the index covers branch and tag tips, so only they can be │ │1713 // searched or paged by commit; an object roots the list itself │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1714 if ((search.len != 0 or from.len != 0) and ref.kind == .object) return null; │ │1728 - // the message page has no diff window or file filter │ │1715 var route = if (std.mem.eql(u8, segments.rest(), message_seg)) │ │1730 - (if (start == 0) repoCommitMessageRoute(pair, ref.kind, ref.value, base_oid) else null) orelse return null │ │1716 + repoCommitMessageRoute(pair, ref.kind, ref.value, base_oid) orelse return null │ │1717 + else if (segments.rest().len == 0) │ │1718 + repoCommitsRoute(pair, ref.kind, ref.value, base_oid) orelse return null │ │1719 else │ │1732 - repoCommitsRoute(pair, ref.kind, ref.value, start, pathValue(segments.rest()) orelse return null, base_oid) orelse return null;│ │1720 + return null; │ │1721 if (search.len != 0) route.repo_commits.search = Array(search_route_max_len).from(search) orelse return null; │ │1722 if (from.len != 0) route.repo_commits.from = Array(ref_route_max_len).from(from) orelse return null; │ │1723 return route; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1761 std.mem.eql(u8, a_c.search.slice(), b.repo_commits.search.slice()) and │ │1762 std.mem.eql(u8, a_c.from.slice(), b.repo_commits.from.slice()) and │ │1763 std.mem.eql(u8, a_c.merge.slice(), b.repo_commits.merge.slice()) and │ │1776 - switch (a_c.content) { │ │1777 - .diff => |a_d| switch (b.repo_commits.content) { │ │1778 - .diff => |b_d| a_d.start == b_d.start and std.mem.eql(u8, a_d.path.slice(), b_d.path.slice()),│ │1779 - .message => false, │ │1780 - }, │ │1781 - .message => std.meta.activeTag(b.repo_commits.content) == .message, │ │1782 - }, │ │1764 + a_c.message == b.repo_commits.message, │ │1765 .repo_diff => |a_d| std.mem.eql(u8, a_d.name.slice(), b.repo_diff.name.slice()) and │ │1766 a_d.ref_or_oid == b.repo_diff.ref_or_oid and │ │1767 std.mem.eql(u8, a_d.value.slice(), b.repo_diff.value.slice()) and │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1806 .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()),│ │1807 .fork_diff => |a_d| std.mem.eql(u8, a_d.fork.name.slice(), b.fork_diff.fork.name.slice()) and │ │1808 std.mem.eql(u8, a_d.fork.id.slice(), b.fork_diff.fork.id.slice()) and │ │1809 + std.mem.eql(u8, a_d.oid.slice(), b.fork_diff.oid.slice()) and │ │1810 + std.mem.eql(u8, a_d.base_oid.slice(), b.fork_diff.base_oid.slice()) and │ │1811 a_d.start == b.fork_diff.start and std.mem.eql(u8, a_d.path.slice(), b.fork_diff.path.slice()), │ │1812 .fork_files => |a_f| std.mem.eql(u8, a_f.fork.name.slice(), b.fork_files.fork.name.slice()) and │ │1813 std.mem.eql(u8, a_f.fork.id.slice(), b.fork_files.fork.id.slice()) and │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1818 .fork_commits => |a_c| std.mem.eql(u8, a_c.fork.name.slice(), b.fork_commits.fork.name.slice()) and │ │1819 std.mem.eql(u8, a_c.fork.id.slice(), b.fork_commits.fork.id.slice()) and │ │1820 std.mem.eql(u8, a_c.oid.slice(), b.fork_commits.oid.slice()) and │ │1837 - switch (a_c.content) { │ │1838 - .diff => |a_d| switch (b.fork_commits.content) { │ │1839 - .diff => |b_d| a_d.start == b_d.start and std.mem.eql(u8, a_d.path.slice(), b_d.path.slice()), │ │1840 - .message => false, │ │1841 - }, │ │1842 - .message => std.meta.activeTag(b.fork_commits.content) == .message, │ │1843 - }, │ │1821 + a_c.message == b.fork_commits.message, │ │1822 .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()),│ │1823 .fork_new_user => |a_f| std.mem.eql(u8, a_f.name.slice(), b.fork_new_user.name.slice()) and std.mem.eql(u8, a_f.id.slice(), b.fork_new_user.id.slice()),│ │1824 .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()), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Fork.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │135 const files_find: []const u8 = switch (route) { │ │136 .fork_files => |*f| f.find.slice(), │ │137 else => "", │ │138 - }; │ │139 - const commits_content: ui.RoutablePage.RepoCommitsRoute.Content = switch (route) { │ │140 - .fork_commits => |c| c.content, │ │141 - else => .{ .diff = .{} }, │ │138 }; │ │139 + const commits_message = route == .fork_commits and route.fork_commits.message; │ │140 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, "", "");│ │141 + var commits = try Commits.init(.xit, repo_opts, arena, fork_repo, io, arena.child_allocator, haxy_moment, handle, requested_ref, requested_value, commits_message, &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, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │147 const diff_path = switch (route) { │ │148 .fork_diff => |*d| try aa.dupe(u8, d.path.slice()), │ │149 else => "", │ │150 + }; │ │151 + // a named commit's diff is against the route's base (all zeros for │ │152 + // none), else the diff is the whole patch's │ │153 + const diff_oid: []const u8, const diff_base_oid: []const u8 = switch (route) { │ │154 + .fork_diff => |*d| .{ try aa.dupe(u8, d.oid.slice()), try aa.dupe(u8, d.base_oid.slice()) }, │ │155 + else => .{ "", "" }, │ │156 }; │ │157 + const diff_base: ?@TypeOf(fork_oid), const diff_head: @TypeOf(fork_oid) = if (diff_oid.len == 0) .{ commits_base_oid, fork_oid } else named: {│ │158 + evt.PatchRev.validateOid(repo_opts.hash, diff_oid) catch return error.NotFound; │ │159 + evt.PatchRev.validateOid(repo_opts.hash, diff_base_oid) catch return error.NotFound; │ │160 + break :named .{ if (std.mem.allEqual(u8, diff_base_oid, '0')) null else diff_base_oid[0..fork_oid.len].*, diff_oid[0..fork_oid.len].* }; │ │161 + }; │ │162 │ │163 return .{ │ │164 .header = try Header.init(arena, target_record.event.name, identity.owner, &id_hex, requested_value), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │166 .commits = commits, │ │167 .patch = patch_data, │ │168 .diff = .{ │ │161 - .route = .{ .fork = .{ .identity = try aa.dupe(u8, identity.identity), .id = try aa.dupe(u8, &id_hex) } }, │ │169 + .route = .{ .fork = .{ .identity = try aa.dupe(u8, identity.identity), .id = try aa.dupe(u8, &id_hex), .oid = diff_oid, .base_oid = diff_base_oid } }, │ │170 .path = diff_path, │ │163 - .window = try Diff.render(.xit, repo_opts, io, arena.child_allocator, aa, fork_repo, &commits_base_oid, fork_oid, diff_start, diff_path), │ │171 + .window = try Diff.render(.xit, repo_opts, io, arena.child_allocator, aa, fork_repo, if (diff_base) |*base| base else null, diff_head, diff_start, diff_path),│ │172 }, │ │173 .auth = Auth.init(), │ │174 .quit = Quit.init(), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Fork/Header.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │100 const current_tag = std.meta.activeTag(session.data.current_page); │ │101 const routes = [_]ui.RoutablePage{ │ │102 ui.RoutablePage.forkPatchRoute(fork_identity, data.id) orelse return error.RouteTooLong, │ │103 - ui.RoutablePage.forkDiffRoute(fork_identity, data.id, 0, "") orelse return error.RouteTooLong, │ │103 + ui.RoutablePage.forkDiffRoute(fork_identity, data.id, "", "", 0, "") orelse return error.RouteTooLong, │ │104 ui.RoutablePage.forkFilesRoute(fork_identity, data.id, data.oid, "", 0) orelse return error.RouteTooLong, │ │105 - ui.RoutablePage.forkCommitsRoute(fork_identity, data.id, data.oid, 0, "") orelse return error.RouteTooLong,│ │105 + ui.RoutablePage.forkCommitsRoute(fork_identity, data.id, data.oid) orelse return error.RouteTooLong, │ │106 }; │ │107 const tags = [_]std.meta.Tag(ui.RoutablePage){ .fork_patch, .fork_diff, .fork_files, .fork_commits }; │ │108 const labels = [_][]const u8{ patch_tab_label, diff_tab_label, files_tab_label, commits_label }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯