File
Blob: src/worker/git/core/commitParse.ts
| 1 | /** |
| 2 | * Author/committer signature extracted from a Git commit object. |
| 3 | * |
| 4 | * Format reference |
| 5 | * - `Name <email> <unix-seconds> <tz>` |
| 6 | * Example: `John Doe <john@example.com> 1694025600 +0000` |
| 7 | */ |
| 8 | export interface Signature { |
| 9 | name: string; |
| 10 | email: string; |
| 11 | when: number; |
| 12 | tz: string; |
| 13 | } |
| 14 | |
| 15 | export interface CommitParsed { |
| 16 | tree: string; |
| 17 | parents: string[]; |
| 18 | author?: Signature; |
| 19 | committer?: Signature; |
| 20 | message: string; |
| 21 | } |
| 22 | |
| 23 | /** |
| 24 | * Parse a Git signature line (author or committer). |
| 25 | * |
| 26 | * @param sig - A single line like `Name <email> 1694025600 +0000` |
| 27 | * @returns A normalized `Signature` or `undefined` if the format does not match |
| 28 | */ |
| 29 | export function parseSignature(sig: string): Signature | undefined { |
| 30 | // Format: Name <email> 1694025600 +0000 |
| 31 | const m = sig.match(/^(.*) <([^>]+)>\s+(\d+)\s+([+-]\d{4})$/); |
| 32 | if (!m) return undefined; |
| 33 | const name = m[1]; |
| 34 | const email = m[2]; |
| 35 | const when = parseInt(m[3], 10); |
| 36 | const tz = m[4]; |
| 37 | return { name, email, when, tz }; |
| 38 | } |
| 39 | |
| 40 | /** |
| 41 | * Parse a commit payload string (without the Git object header). |
| 42 | * |
| 43 | * Expected input |
| 44 | * - The raw text after inflating a `commit` Git object and removing the |
| 45 | * `<type> <len>\0` header. Contains RFC-2822-ish lines and a blank line before |
| 46 | * the message body. |
| 47 | * |
| 48 | * Extracted fields |
| 49 | * - `tree`: the root tree OID the commit points to |
| 50 | * - `parents`: zero or more parent OIDs (first entry is the first-parent) |
| 51 | * - `author`, `committer`: parsed signatures if present |
| 52 | * - `message`: text after the first blank line (may be empty) |
| 53 | */ |
| 54 | export function parseCommitText(text: string): CommitParsed { |
| 55 | const mTree = text.match(/^tree ([0-9a-f]{40})/m); |
| 56 | const tree = mTree ? mTree[1] : ""; |
| 57 | const parents = [...text.matchAll(/^parent ([0-9a-f]{40})/gm)].map((x) => x[1]); |
| 58 | const authorLine = (text.match(/^author (.+)$/m) || [])[1]; |
| 59 | const committerLine = (text.match(/^committer (.+)$/m) || [])[1]; |
| 60 | const author = authorLine ? parseSignature(authorLine) : undefined; |
| 61 | const committer = committerLine ? parseSignature(committerLine) : undefined; |
| 62 | const msgIdx = text.indexOf("\n\n"); |
| 63 | const message = msgIdx >= 0 ? text.slice(msgIdx + 2) : ""; |
| 64 | return { tree, parents, author, committer, message }; |
| 65 | } |