╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │18 // how many diff hunks one window of a commit's diff shows; "next"/"previous"│ │19 // move to the adjacent window. │ │20 const diff_page = 10; │ │21 -// the largest commit message the detail view will read in full. │ │22 -const max_message_size = 64 * 1024; │ │21 +// the largest commit message a log page's detail view reads; a longer one is│ │22 +// cut here, with the message page carrying the whole thing. │ │23 +pub const max_message_size = 2 * 1024; │ │24 +// the same for the message page, which only reads the one commit. │ │25 +const max_full_message_size = 100 * 1024; │ │26 │ │27 pub const Hunk = struct { │ │28 path: ?[]const u8 = null, // the file this hunk belongs to │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │35 oid: []const u8, │ │36 date: []const u8, // "YYYY-MM-DD" │ │37 message: []const u8, // trimmed, may be multi-line │ │38 + // whether `message` is only the first line of a message too big to read.│ │39 + message_truncated: bool = false, │ │40 hunks: []const Hunk, │ │41 // the hunk index this window starts at (0 = the first window). │ │42 window_start: usize, │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │51 // didn't name one), so the page can canonicalize its url to it. │ │52 ref_or_oid: ui.RoutablePage.RefOrOid, │ │53 ref_or_oid_value: []const u8, │ │49 -// the file the top commit's diff is filtered to ("" = every file). │ │50 -path: []const u8 = "", │ │54 commits: []const Commit, │ │55 // the first oid of the next page, or null when this is the last page.│ │56 next_start: ?[]const u8, │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────╮ │57 +// what the pane shows for the commit the log walks from. │ │58 +content: Content = .{ .diff = .{} }, │ │59 // the "viewing <ref> <value>" banner shown above the log.│ │60 header: Header, │ │61 │ ╰──────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │62 const Self = @This(); │ │63 │ │64 +// the diff and the message are alternatives, so a filtered diff can't also be│ │65 +// a message. the message is read whole, however big, since it's all its pane │ │66 +// shows. │ │67 +pub const Content = union(enum) { │ │68 + diff: struct { │ │69 + // the file the top commit's diff is filtered to ("" = every file). │ │70 + path: []const u8 = "", │ │71 + }, │ │72 + message, │ │73 +}; │ │74 + │ │75 // walk the log for an opened repo. generic over the repo's backend and hash │ │76 // kind so the oid buffers and diff types it threads through match the repo's │ │61 -// opts. `start` is the hunk index the selected commit's (the first one, the │ │62 -// walk root) diff window starts at; 0 is the first window, moved by │ │63 -// "next"/"previous". walks with the arena's backing allocator (transient; the│ │77 +// opts. `content` is what the walk root's pane shows, the only commit any of │ │78 +// it applies to. walks with the arena's backing allocator (transient; the │ │79 // commits we keep are duped into the page arena so they outlive it). │ │80 pub fn init( │ │81 comptime repo_kind: rp.RepoKind, │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │87 identity: []const u8, │ │88 requested_ref_or_oid: ?ui.RoutablePage.RefOrOid, │ │89 requested_value: []const u8, │ │75 - start: usize, │ │76 - path: []const u8, │ │90 + content: ui.RoutablePage.RepoCommitsRoute.Content, │ │91 ) !Self { │ │92 const aa = arena.allocator(); │ │93 const hex_len = ui.ResolvedRefOrOid(repo_kind, repo_opts).hex_len;│ ╰──────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │ 94 + │ │ 95 + // the walk root's message replaces its diff, so it's read whole and its │ │ 96 + // hunks aren't rendered. the window and the filter are its diff's. │ │ 97 + const root_message = std.meta.activeTag(content) == .message; │ │ 98 + const root_start: usize = switch (content) { │ │ 99 + .diff => |d| d.start, │ │100 + .message => 0, │ │101 + }; │ │102 + const root_path: []const u8 = switch (content) { │ │103 + .diff => |d| d.path.slice(), │ │104 + .message => "", │ │105 + }; │ │106 │ │107 // resolve the requested ref (or the default branch) to the commit oid to│ │108 // walk from. an explicitly named ref that doesn't resolve is a bad url │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │109 // (NotFound -> 404); the default-branch path falls through to empty. │ │110 const resolved = (try ui.ResolvedRefOrOid(repo_kind, repo_opts).init(repo, io, aa, requested_ref_or_oid, requested_value)) orelse {│ │111 if (requested_ref_or_oid != null) return error.NotFound; │ │ 86 - return emptyResult(aa, identity, .branch, requested_value, path); │ │112 + return emptyResult(aa, identity, .branch, requested_value, content); │ │113 }; │ │114 var start_arr = [1][hex_len]u8{resolved.oid}; │ │115 const start_oids: []const [hex_len]u8 = start_arr[0..1]; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │122 var count: usize = 0; │ │123 var next_start: ?[]const u8 = null; │ │124 { │ │ 99 - var iter = repo.log(io, gpa, start_oids) catch return emptyResult(aa, identity, resolved.ref_or_oid, resolved.value, path); │ │125 + var iter = repo.log(io, gpa, start_oids) catch return emptyResult(aa, identity, resolved.ref_or_oid, resolved.value, content);│ │126 defer iter.deinit(); │ │127 while (try iter.next(gpa)) |commit_object| { │ │128 defer commit_object.deinit(); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │132 } │ │133 const md = commit_object.content.commit.metadata; │ │134 @memcpy(&oids[count], &commit_object.oid); │ │135 + // only the message the pane shows is read past the log's limit. │ │136 + const text, const truncated = try readMessage(repo_kind, repo_opts, aa, commit_object, if (root_message and count == 0) max_full_message_size else max_message_size);│ │137 buf[count] = .{ │ │138 .oid = try aa.dupe(u8, &commit_object.oid), │ │139 .date = try formatDate(aa, md.timestamp), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯