add committer info to commit detail view
Shows committer name, email, and date when they differ from the author (e.g. rebased or cherry-picked commits). Escapes angle brackets in author/committer identity strings via escapeHtml. Fixes audio controls attribute type mismatch in FileBlob. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Msrc/index.tsx
| @@ -23,8 +23,7 @@ for (const cmd of ["git", "ssh-keygen"]) { | |||
|---|---|---|---|
| 23 | 23 | ||
| 24 | 24 | await syncStartup(); | |
| 25 | 25 | if (!SSH_DISABLED) await startSshServer(); | |
| 26 | - | ||
| 27 | - | const _app = new Elysia({ serve: { maxRequestBodySize: MAX_UPLOAD_BYTES } }) | |
| 26 | + | new Elysia({ serve: { maxRequestBodySize: MAX_UPLOAD_BYTES } }) | |
| 28 | 27 | .use( | |
| 29 | 28 | staticPlugin({ | |
| 30 | 29 | assets: path.resolve("./public"), | |
| @@ -40,5 +39,4 @@ const _app = new Elysia({ serve: { maxRequestBodySize: MAX_UPLOAD_BYTES } }) | |||
|---|---|---|---|
| 40 | 39 | .use(releasesRoutes) | |
| 41 | 40 | .use(avatarRoutes) | |
| 42 | 41 | .listen(PORT); | |
| 43 | - | ||
| 44 | 42 | console.log(`Hearthforge running at http://localhost:${PORT}`); | |
Msrc/services/git.ts
| @@ -115,6 +115,9 @@ export interface CommitMeta { | |||
|---|---|---|---|
| 115 | 115 | author: string; | |
| 116 | 116 | email: string; | |
| 117 | 117 | date: string; | |
| 118 | + | committer: string; | |
| 119 | + | committerEmail: string; | |
| 120 | + | committerDate: string; | |
| 118 | 121 | parents: string[]; | |
| 119 | 122 | } | |
| 120 | 123 | ||
| @@ -433,7 +436,7 @@ export const git = { | |||
|---|---|---|---|
| 433 | 436 | const p = repoPath(name); | |
| 434 | 437 | try { | |
| 435 | 438 | const [metaOut, msgOut] = await Promise.all([ | |
| 436 | - | $`git -C ${p} show --no-patch --format=%H%x1f%an%x1f%ae%x1f%ai%x1f%P ${sha}`.text(), | |
| 439 | + | $`git -C ${p} show --no-patch --format=%H%x1f%an%x1f%ae%x1f%ai%x1f%cn%x1f%ce%x1f%ci%x1f%P ${sha}`.text(), | |
| 437 | 440 | $`git -C ${p} log --format=%B -1 ${sha}`.text(), | |
| 438 | 441 | ]); | |
| 439 | 442 | const parts = metaOut.trim().split("\x1f"); | |
| @@ -454,7 +457,10 @@ export const git = { | |||
|---|---|---|---|
| 454 | 457 | author: parts[1] ?? "", | |
| 455 | 458 | email: parts[2] ?? "", | |
| 456 | 459 | date: parts[3] ?? "", | |
| 457 | - | parents: (parts[4] ?? "").trim().split(/\s+/).filter(Boolean), | |
| 460 | + | committer: parts[4] ?? "", | |
| 461 | + | committerEmail: parts[5] ?? "", | |
| 462 | + | committerDate: parts[6] ?? "", | |
| 463 | + | parents: (parts[7] ?? "").trim().split(/\s+/).filter(Boolean), | |
| 458 | 464 | }; | |
| 459 | 465 | } catch { | |
| 460 | 466 | return null; | |
Msrc/views/patches/PatchDetail.tsx
| @@ -178,7 +178,11 @@ export function PatchDetail({ | |||
|---|---|---|---|
| 178 | 178 | </div> | |
| 179 | 179 | <div class="patch-author-meta"> | |
| 180 | 180 | <span class="patch-author-label">git author:</span> | |
| 181 | - | <span class="patch-author-identity">{escapeHtml(`${patch.author_name} <${patch.author_email}>`)}</span> | |
| 181 | + | <span class="patch-author-identity"> | |
| 182 | + | {escapeHtml( | |
| 183 | + | `${patch.author_name} <${patch.author_email}>`, | |
| 184 | + | )} | |
| 185 | + | </span> | |
| 182 | 186 | </div> | |
| 183 | 187 | </div> | |
| 184 | 188 | ||
Msrc/views/repos/CommitDetail.tsx
| @@ -1,3 +1,4 @@ | |||
|---|---|---|---|
| 1 | + | import { escapeHtml } from "@kitajs/html"; | |
| 1 | 2 | import type { RepositoryRow } from "../../db/index.ts"; | |
| 2 | 3 | import type { SessionUser } from "../../middleware/session.ts"; | |
| 3 | 4 | import type { RenderedDiffFile } from "../../services/diffHighlight.ts"; | |
| @@ -66,7 +67,7 @@ export function CommitDetail({ | |||
|---|---|---|---|
| 66 | 67 | <div class="commit-card-meta-row"> | |
| 67 | 68 | <span class="commit-meta-label">Author</span> | |
| 68 | 69 | <span class="commit-meta-value"> | |
| 69 | - | {meta.author} <{meta.email}> | |
| 70 | + | {escapeHtml(`${meta.author} <${meta.email}>`)} | |
| 70 | 71 | </span> | |
| 71 | 72 | </div> | |
| 72 | 73 | <div class="commit-card-meta-row"> | |
| @@ -78,6 +79,32 @@ export function CommitDetail({ | |||
|---|---|---|---|
| 78 | 79 | {formatDate(meta.date)} | |
| 79 | 80 | </time> | |
| 80 | 81 | </div> | |
| 82 | + | {(meta.committer !== meta.author || | |
| 83 | + | meta.committerEmail !== meta.email) && ( | |
| 84 | + | <> | |
| 85 | + | <div class="commit-card-meta-row"> | |
| 86 | + | <span class="commit-meta-label"> | |
| 87 | + | Committer | |
| 88 | + | </span> | |
| 89 | + | <span class="commit-meta-value"> | |
| 90 | + | {escapeHtml( | |
| 91 | + | `${meta.committer} <${meta.committerEmail}>`, | |
| 92 | + | )} | |
| 93 | + | </span> | |
| 94 | + | </div> | |
| 95 | + | <div class="commit-card-meta-row"> | |
| 96 | + | <span class="commit-meta-label"> | |
| 97 | + | Commit date | |
| 98 | + | </span> | |
| 99 | + | <time | |
| 100 | + | class="commit-meta-value" | |
| 101 | + | datetime={meta.committerDate} | |
| 102 | + | > | |
| 103 | + | {formatDate(meta.committerDate)} | |
| 104 | + | </time> | |
| 105 | + | </div> | |
| 106 | + | </> | |
| 107 | + | )} | |
| 81 | 108 | <div class="commit-card-meta-row"> | |
| 82 | 109 | <span class="commit-meta-label">Commit</span> | |
| 83 | 110 | <code class="commit-meta-value commit-sha-full mono"> | |
Msrc/views/repos/FileBlob.tsx
| @@ -89,7 +89,7 @@ export function FileBlob({ | |||
|---|---|---|---|
| 89 | 89 | ) : view.mimeType.startsWith("audio/") ? ( | |
| 90 | 90 | // biome-ignore lint/a11y/useMediaCaption: captions unavailable for arbitrary repo files | |
| 91 | 91 | <audio | |
| 92 | - | controls | |
| 92 | + | controls="true" | |
| 93 | 93 | src={`/${repo.name}/raw/${blobRef}/${filePath}`} | |
| 94 | 94 | class="file-media-audio" | |
| 95 | 95 | /> | |