Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge branch 'worktree-agent-a19b06d959c105bf7' into claude/cool-newton-7nm8r9 | 1 | /** |
| 2 | * A gzipped tar archive of files, made in memory, as `git archive` makes | |
| 3 | * one: POSIX ustar headers, a pax global header carrying the commit | |
| 4 | * (`comment=<sha>`, which `git get-tar-commit-id` reads), a directory | |
| 5 | * entry before each folder's files, executable files and symlinks kept as | |
| 6 | * they are in git, and a pax extended header for a path or link target too | |
| 7 | * long for ustar. Everything is owned by root and dated by the commit. | |
| 8 | * Enough for downloading a commit: each file stays under 8 GB (the caller | |
| 9 | * caps them far below that). | |
| 10 | */ | |
| 11 | ||
| 12 | export type TarEntry = { | |
| 13 | /** The path in the archive, folders separated by `/`. */ | |
| 14 | path: string; | |
| 15 | /** The file's bytes; for a symlink, where it points. */ | |
| 16 | data: Uint8Array; | |
| 17 | kind?: "blob" | "exec" | "symlink"; | |
| 18 | }; | |
| 19 | ||
| 20 | export type TarOptions = { | |
| 21 | /** Seconds since the epoch, what every entry is dated. */ | |
| 22 | mtime: number; | |
| 23 | /** The commit, kept in a pax global header as `git archive` keeps it. */ | |
| 24 | comment?: string; | |
| 25 | }; | |
| 26 | ||
| 27 | const BLOCK = 512; | |
| 28 | const encoder = new TextEncoder(); | |
| 29 | ||
| 30 | /** `git archive`'s modes, with its default umask of 002. */ | |
| 31 | const MODES = { blob: 0o664, exec: 0o775, symlink: 0o777, dir: 0o775 } as const; | |
| 32 | ||
| 33 | /** `value` in octal, zero-padded to fill `width` bytes with a NUL at the end. */ | |
| 34 | function octal(value: number, width: number): string { | |
| 35 | return value.toString(8).padStart(width - 1, "0") + "\0"; | |
| 36 | } | |
| 37 | ||
| 38 | function put(header: Uint8Array, at: number, width: number, value: string | Uint8Array) { | |
| 39 | const bytes = typeof value === "string" ? encoder.encode(value) : value; | |
| 40 | header.set(bytes.subarray(0, width), at); | |
| 41 | } | |
| 42 | ||
| 43 | /** | |
| 44 | * `path` as ustar's `name` (100 bytes) and `prefix` (155), split at a | |
| 45 | * slash; null when it does not fit. | |
| 46 | */ | |
| 47 | export function splitPath(path: Uint8Array): { name: Uint8Array; prefix: Uint8Array } | null { | |
| 48 | if (path.length <= 100) return { name: path, prefix: new Uint8Array() }; | |
| 49 | // The last slash that leaves at most 100 bytes after it and 155 before. | |
| 50 | for (let at = Math.min(path.length - 1, 155); at > 0; at--) { | |
| 51 | if (path[at] !== 0x2f) continue; | |
| 52 | if (path.length - at - 1 > 100) return null; | |
| 53 | if (path.length - at - 1 === 0) continue; | |
| 54 | return { name: path.subarray(at + 1), prefix: path.subarray(0, at) }; | |
| 55 | } | |
| 56 | return null; | |
| 57 | } | |
| 58 | ||
| 59 | type Header = { name: Uint8Array; prefix?: Uint8Array; mode: number; size: number; mtime: number; type: string; link?: Uint8Array }; | |
| 60 | ||
| 61 | function header(entry: Header): Uint8Array { | |
| 62 | const out = new Uint8Array(BLOCK); | |
| 63 | put(out, 0, 100, entry.name); | |
| 64 | put(out, 100, 8, octal(entry.mode, 8)); | |
| 65 | put(out, 108, 8, octal(0, 8)); | |
| 66 | put(out, 116, 8, octal(0, 8)); | |
| 67 | put(out, 124, 12, octal(entry.size, 12)); | |
| 68 | put(out, 136, 12, octal(entry.mtime, 12)); | |
| 69 | put(out, 148, 8, " "); | |
| 70 | put(out, 156, 1, entry.type); | |
| 71 | if (entry.link) put(out, 157, 100, entry.link); | |
| 72 | put(out, 257, 6, "ustar\0"); | |
| 73 | put(out, 263, 2, "00"); | |
| 74 | put(out, 265, 32, "root"); | |
| 75 | put(out, 297, 32, "root"); | |
| 76 | put(out, 329, 8, octal(0, 8)); | |
| 77 | put(out, 337, 8, octal(0, 8)); | |
| 78 | if (entry.prefix) put(out, 345, 155, entry.prefix); | |
| 79 | let sum = 0; | |
| 80 | for (const byte of out) sum += byte; | |
| 81 | put(out, 148, 8, sum.toString(8).padStart(6, "0") + "\0 "); | |
| 82 | return out; | |
| 83 | } | |
| 84 | ||
| 85 | /** One pax record, `<length> <key>=<value>\n`, its length counting itself. */ | |
| 86 | export function paxRecord(key: string, value: Uint8Array): Uint8Array { | |
| 87 | const rest = encoder.encode(` ${key}=`).length + value.length + 1; | |
| 88 | let length = rest + String(rest).length; | |
| 89 | if (String(length).length !== String(rest).length) length = rest + String(length).length; | |
| 90 | const out = new Uint8Array(length); | |
| 91 | const head = encoder.encode(`${length} ${key}=`); | |
| 92 | out.set(head); | |
| 93 | out.set(value, head.length); | |
| 94 | out[length - 1] = 0x0a; | |
| 95 | return out; | |
| 96 | } | |
| 97 | ||
| 98 | function padded(data: Uint8Array): Uint8Array[] { | |
| 99 | const rest = data.length % BLOCK; | |
| 100 | return rest === 0 ? [data] : [data, new Uint8Array(BLOCK - rest)]; | |
| 101 | } | |
| 102 | ||
| 103 | function concat(parts: Uint8Array[]): Uint8Array<ArrayBuffer> { | |
| 104 | const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0)); | |
| 105 | let at = 0; | |
| 106 | for (const part of parts) { | |
| 107 | out.set(part, at); | |
| 108 | at += part.length; | |
| 109 | } | |
| 110 | return out; | |
| 111 | } | |
| 112 | ||
| 113 | /** The tar archive of `entries` (uncompressed), each folder's entry before its files. */ | |
| 114 | export function tar(entries: TarEntry[], options: TarOptions): Uint8Array<ArrayBuffer> { | |
| 115 | const parts: Uint8Array[] = []; | |
| 116 | const mtime = Math.max(0, Math.floor(options.mtime)); | |
| 117 | if (options.comment) { | |
| 118 | const record = paxRecord("comment", encoder.encode(options.comment)); | |
| 119 | parts.push(header({ name: encoder.encode("pax_global_header"), mode: 0o666, size: record.length, mtime, type: "g" }), ...padded(record)); | |
| 120 | } | |
| 121 | const add = (path: string, kind: "blob" | "exec" | "symlink" | "dir", data: Uint8Array) => { | |
| 122 | const fullPath = encoder.encode(kind === "dir" ? `${path}/` : path); | |
| 123 | const link = kind === "symlink" ? data : undefined; | |
| 124 | const body = kind === "blob" || kind === "exec" ? data : new Uint8Array(); | |
| 125 | const split = splitPath(fullPath); | |
| 126 | const records: Uint8Array[] = []; | |
| 127 | if (!split) records.push(paxRecord("path", fullPath)); | |
| 128 | if (link && link.length > 100) records.push(paxRecord("linkpath", link)); | |
| 129 | if (records.length > 0) { | |
| 130 | const pax = concat(records); | |
| 131 | const name = encoder.encode("././@PaxHeader"); | |
| 132 | parts.push(header({ name, mode: 0o666, size: pax.length, mtime, type: "x" }), ...padded(pax)); | |
| 133 | } | |
| 134 | const type = kind === "dir" ? "5" : kind === "symlink" ? "2" : "0"; | |
| 135 | parts.push( | |
| 136 | header({ | |
| 137 | name: split?.name ?? fullPath.subarray(0, 100), | |
| 138 | prefix: split?.prefix, | |
| 139 | mode: MODES[kind], | |
| 140 | size: body.length, | |
| 141 | mtime, | |
| 142 | type, | |
| 143 | link: link && link.length <= 100 ? link : link?.subarray(0, 100), | |
| 144 | }), | |
| 145 | ); | |
| 146 | if (body.length > 0) parts.push(...padded(body)); | |
| 147 | }; | |
| 148 | const folders = new Set<string>(); | |
| 149 | for (const entry of entries) { | |
| 150 | const segments = entry.path.split("/").filter(Boolean); | |
| 151 | for (let depth = 1; depth < segments.length; depth++) { | |
| 152 | const folder = segments.slice(0, depth).join("/"); | |
| 153 | if (folders.has(folder)) continue; | |
| 154 | folders.add(folder); | |
| 155 | add(folder, "dir", new Uint8Array()); | |
| 156 | } | |
| 157 | add(segments.join("/"), entry.kind ?? "blob", entry.data); | |
| 158 | } | |
| 159 | parts.push(new Uint8Array(BLOCK * 2)); | |
| 160 | return concat(parts); | |
| 161 | } | |
| 162 | ||
| 163 | /** {@link tar}, gzipped. */ | |
| 164 | export async function tarGz(entries: TarEntry[], options: TarOptions): Promise<Uint8Array<ArrayBuffer>> { | |
| 165 | const stream = new Blob([tar(entries, options)]).stream().pipeThrough(new CompressionStream("gzip")); | |
| 166 | return new Uint8Array(await new Response(stream).arrayBuffer()); | |
| 167 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.