CommitDetail.tsx
Raw
1import { escapeHtml } from "@kitajs/html";
2import type { RepositoryRow } from "../../db/index.ts";
3import { formatDateTime } from "../../lib/formatDate.ts";
4import type { SessionUser } from "../../middleware/session.ts";
5import type { RenderedDiffFile } from "../../services/diffHighlight.ts";
6import type { CommitMeta } from "../../services/git.ts";
7import { DiffView } from "../DiffView.tsx";
8import { Layout } from "../layout.tsx";
9import { RepoHeader } from "../repos/RepoHeader.tsx";
10import { RepoNav } from "./RepoNav.tsx";
11
12interface CommitDetailProps {
13 user: SessionUser | null;
14 repo: RepositoryRow;
15 sha: string;
16 meta: CommitMeta;
17 files: RenderedDiffFile[];
18 tooLarge?: number;
19}
20
21export function CommitDetail({
22 user,
23 repo,
24 sha,
25 meta,
26 files,
27 tooLarge,
28}: CommitDetailProps) {
29 return (
30 <Layout user={user} title={`${sha.slice(0, 7)} — ${repo.name}`}>
31 <div class="container">
32 <RepoHeader repo={repo} />
33 <RepoNav repo={repo} active="commits" user={user} />
34
35 {/* Back link */}
36 <div class="commit-page-top">
37 <a
38 href={`/${repo.name}/commits/${repo.default_branch}`}
39 class="btn btn-sm"
40 >
41 ← Back to log
42 </a>
43 <a
44 href={`/${repo.name}/tree/${sha}`}
45 class="btn btn-secondary btn-sm"
46 title={`Browse tree at ${sha.slice(0, 7)}`}
47 safe
48 >
49 @ {sha.slice(0, 7)}
50 </a>
51 </div>
52
53 {/* Commit metadata card */}
54 <div class="commit-card">
55 <h2
56 class={`commit-card-subject${meta.subject ? "" : " commit-card-subject-empty"}`}
57 safe
58 >
59 {meta.subject || "No commit message"}
60 </h2>
61 {!!meta.body && (
62 <pre class="commit-card-body" safe>
63 {meta.body}
64 </pre>
65 )}
66 <div class="commit-card-meta">
67 <div class="commit-card-meta-row">
68 <span class="commit-meta-label">Author</span>
69 <span class="commit-meta-value">
70 {escapeHtml(`${meta.author} <${meta.email}>`)}
71 </span>
72 </div>
73 <div class="commit-card-meta-row">
74 <span class="commit-meta-label">Date</span>
75 <time
76 class="commit-meta-value"
77 datetime={meta.date}
78 safe
79 >
80 {formatDateTime(meta.date)}
81 </time>
82 </div>
83 {(meta.committer !== meta.author ||
84 meta.committerEmail !== meta.email) && (
85 <>
86 <div class="commit-card-meta-row">
87 <span class="commit-meta-label">
88 Committer
89 </span>
90 <span class="commit-meta-value">
91 {escapeHtml(
92 `${meta.committer} <${meta.committerEmail}>`,
93 )}
94 </span>
95 </div>
96 <div class="commit-card-meta-row">
97 <span class="commit-meta-label">
98 Commit date
99 </span>
100 <time
101 class="commit-meta-value"
102 datetime={meta.committerDate}
103 safe
104 >
105 {formatDateTime(meta.committerDate)}
106 </time>
107 </div>
108 </>
109 )}
110 <div class="commit-card-meta-row">
111 <span class="commit-meta-label">Commit</span>
112 <code
113 class="commit-meta-value commit-sha-full mono"
114 safe
115 >
116 {meta.hash}
117 </code>
118 </div>
119 {meta.parents.length > 0 && (
120 <div class="commit-card-meta-row">
121 <span class="commit-meta-label">
122 Parent{meta.parents.length > 1 ? "s" : ""}
123 </span>
124 <span class="commit-meta-value">
125 {meta.parents.map((p) => (
126 <a
127 href={`/${repo.name}/commit/${p}`}
128 class="commit-hash mono"
129 safe
130 >
131 {p.slice(0, 7)}
132 </a>
133 ))}
134 </span>
135 </div>
136 )}
137 {meta.sigStatus !== "none" && (
138 <div class="commit-card-meta-row">
139 <span class="commit-meta-label">Signature</span>
140 <span class="commit-meta-value">
141 {meta.sigStatus === "good" ? (
142 <span class="sig-badge verified">
143 verified
144 </span>
145 ) : (
146 <span class="sig-badge unverified">
147 unverified
148 </span>
149 )}
150 </span>
151 </div>
152 )}
153 </div>
154 </div>
155
156 {tooLarge !== undefined ? (
157 <div class="file-download-notice">
158 <p safe>
159 Diff is too large to render inline (
160 {(tooLarge / 1024 / 1024).toFixed(1)} MB). Browse
161 individual files at the tree below.
162 </p>
163 </div>
164 ) : (
165 <DiffView files={files} repo={repo} sha={sha} />
166 )}
167 </div>
168 </Layout>
169 );
170}
171