╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │15 │ │16 // how many commits a page shows before a "next" link appears. │ │17 const page_size = 20; │ │18 -// how many diff hunks one window of a commit's diff shows; "next"/"previous"│ │19 -// move to the adjacent window. │ │18 // the largest message read even when it has fewer than the preview's line │ │19 // limit. │ │20 const max_message_size = 100 * 1024; │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │38 // didn't name one), so the page can canonicalize its url to it. │ │39 ref_or_oid: ui.RoutablePage.RefOrOid, │ │40 ref_or_oid_value: []const u8, │ │41 +base_oid: []const u8 = "", │ │42 commit_count: ?u64 = null, │ │43 commits: []const Commit, │ │44 // the first oid of the next page, or null when this is the last page.│ ╰──────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────╮ │45 next_start: ?[]const u8, │ │46 // what the pane shows for the commit the log walks from. │ │47 content: Content = .{ .diff = .{} }, │ │49 -// the "viewing <ref> <value>" banner shown above the log.│ │48 +// the ref banner shown above the log. │ │49 header: Header, │ │50 │ │51 const Self = @This(); │ ╰──────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │79 requested_ref_or_oid: ?ui.RoutablePage.RefOrOid, │ │80 requested_value: []const u8, │ │81 content: ui.RoutablePage.RepoCommitsRoute.Content, │ │83 - base_oid_maybe: ?[xit.hash.hexLen(repo_opts.hash)]u8, │ │82 + base_oid: []const u8, │ │83 ) !Self { │ │84 const aa = arena.allocator(); │ │85 const hex_len = ui.ResolvedRefOrOid(repo_kind, repo_opts).hex_len;│ ╰──────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │86 + if (base_oid.len != 0) { │ │87 + evt.PatchRev.validateOid(repo_opts.hash, base_oid) catch return error.NotFound;│ │88 + } │ │89 │ │90 // the walk root's message replaces its diff, so it's read whole and its │ │91 // hunks aren't rendered. the window and the filter are its diff's. │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │102 // resolve the requested ref (or the default branch) to the commit oid to │ │103 // walk from. an explicitly named ref that doesn't resolve is a bad url │ │104 // (NotFound -> 404); the default-branch path falls through to empty. │ │103 - const resolved = (try ui.ResolvedRefOrOid(repo_kind, repo_opts).init(repo, io, aa, requested_ref_or_oid, requested_value)) orelse {│ │105 + var resolved = (try ui.ResolvedRefOrOid(repo_kind, repo_opts).init(repo, io, aa, requested_ref_or_oid, requested_value)) orelse { │ │106 if (requested_ref_or_oid != null) return error.NotFound; │ │105 - return emptyResult(aa, location, .branch, requested_value, content); │ │107 + return emptyResult(aa, location, .branch, requested_value, content, base_oid); │ │108 }; │ │107 - var start_arr = [1][hex_len]u8{resolved.oid}; │ │108 - const start_oids: []const [hex_len]u8 = start_arr[0..1]; │ │109 │ │110 // collect this page's commit metadata, plus a peek at the one after it (its │ │111 // oid is the next page's start). the diff for each is rendered afterward, so │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │115 var count: usize = 0; │ │116 var next_start: ?[]const u8 = null; │ │117 { │ │118 - var iter = repo.log(io, gpa, .{ .start_oids = start_oids, .first_parent = true }) catch return emptyResult(aa, location, resolved.ref_or_oid, resolved.value, content); │ │118 + var iter = repo.log(io, gpa, .{ .start_oids = &.{resolved.oid}, .first_parent = true }) catch return emptyResult(aa, location, resolved.ref_or_oid, resolved.value, content, base_oid);│ │119 defer iter.deinit(); │ │120 - // omit the base and its ancestors │ │121 - if (base_oid_maybe) |base_oid| try iter.exclude(&base_oid); │ │120 while (try iter.next(gpa)) |commit_object| { │ │121 defer commit_object.deinit(); │ │122 + // count from the commit itself when the tip is an annotated tag │ │123 + if (count == 0) resolved.oid = commit_object.oid; │ │124 + // the base is a stopping point, not an ancestry exclusion │ │125 + if (std.mem.eql(u8, &commit_object.oid, base_oid)) break; │ │126 if (count == page_size) { │ │127 next_start = try aa.dupe(u8, &commit_object.oid); │ │128 break; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │159 .location = try location.dupe(aa), │ │160 .ref_or_oid = resolved.ref_or_oid, │ │161 .ref_or_oid_value = resolved.value, │ │160 - .commit_count = if (repo_kind == .xit and base_oid_maybe == null) │ │161 - repo.commitCount(io, gpa, .{ .oid = &resolved.oid }) catch null │ │162 - else │ │163 - null, │ │162 + .base_oid = try aa.dupe(u8, base_oid), │ │163 + .commit_count = if (repo_kind == .xit and location == .repo) blk: { │ │164 + if (base_oid.len == 0) break :blk repo.commitCount(io, gpa, .{ .oid = &resolved.oid }) catch null; │ │165 + var moment = repo.core.latestMoment() catch break :blk null; │ │166 + const state = rp.Repo(.xit, repo_opts).State(.read_only){ .core = &repo.core, .extra = .{ .moment = &moment } };│ │167 + break :blk evt.PatchRev.commitCount(repo_kind, repo_opts, state, io, gpa, base_oid, &resolved.oid) catch null; │ │168 + } else null, │ │169 .commits = try aa.dupe(Commit, buf[0..count]), │ │170 .next_start = next_start, │ │171 .content = try pageContent(aa, content), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │174 } │ │175 │ │176 // an empty listing pinned to a ref, for the wasm / no-repo / unresolved paths. │ │172 -pub fn emptyResult(aa: std.mem.Allocator, location: ui.RoutablePage.RepoLocation, ref_or_oid: ui.RoutablePage.RefOrOid, value: []const u8, content: ui.RoutablePage.RepoCommitsRoute.Content) !Self { │ │177 +pub fn emptyResult(aa: std.mem.Allocator, location: ui.RoutablePage.RepoLocation, ref_or_oid: ui.RoutablePage.RefOrOid, value: []const u8, content: ui.RoutablePage.RepoCommitsRoute.Content, base_oid: []const u8) !Self {│ │178 return .{ │ │179 .location = try location.dupe(aa), │ │180 .ref_or_oid = ref_or_oid, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │181 .ref_or_oid_value = try aa.dupe(u8, value),│ │182 + .base_oid = try aa.dupe(u8, base_oid), │ │183 .commit_count = null, │ │184 .commits = &.{}, │ │185 .next_start = null, │ ╰────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯