File
Blob: src/worker/do/repo/debug/packed.ts
| 1 | import type { IdxView, PackCatalogRow } from "@/worker/git/object-store/types"; |
| 2 | |
| 3 | import { inflate } from "@/worker/common"; |
| 4 | import { applyGitDelta } from "@/worker/git/object-store/delta"; |
| 5 | import { |
| 6 | findOidIndex, |
| 7 | findOffsetIndex, |
| 8 | getNextOffset, |
| 9 | getNextOffsetByIndex, |
| 10 | getOidHexAt, |
| 11 | loadIdxView, |
| 12 | } from "@/worker/git/object-store/idxView"; |
| 13 | import { typeCodeToObjectType } from "@/worker/git/object-store/support"; |
| 14 | import { readPackHeaderExFromBuf, readPackRange } from "@/worker/git/pack/packMeta"; |
| 15 | |
| 16 | type DebugResolvedLocation = { |
| 17 | pack: PackCatalogRow; |
| 18 | idx: IdxView; |
| 19 | objectIndex: number; |
| 20 | offset: number; |
| 21 | nextOffset: number; |
| 22 | oid: string; |
| 23 | }; |
| 24 | |
| 25 | async function findCatalogObject( |
| 26 | env: Env, |
| 27 | rows: PackCatalogRow[], |
| 28 | oid: string |
| 29 | ): Promise<DebugResolvedLocation | undefined> { |
| 30 | const oidLc = oid.toLowerCase(); |
| 31 | for (const pack of rows) { |
| 32 | const idx = await loadIdxView(env, pack.packKey, undefined, pack.packBytes); |
| 33 | if (!idx) continue; |
| 34 | const objectIndex = findOidIndex(idx, oidLc); |
| 35 | if (objectIndex < 0) continue; |
| 36 | const offset = idx.offsets[objectIndex]; |
| 37 | const noff = getNextOffset(idx, offset); |
| 38 | if (noff === undefined) continue; |
| 39 | return { |
| 40 | pack, |
| 41 | idx, |
| 42 | objectIndex, |
| 43 | offset, |
| 44 | nextOffset: noff, |
| 45 | oid: oidLc, |
| 46 | }; |
| 47 | } |
| 48 | return undefined; |
| 49 | } |
| 50 | |
| 51 | async function readPackedObjectFromLocation( |
| 52 | env: Env, |
| 53 | location: DebugResolvedLocation, |
| 54 | rows: PackCatalogRow[], |
| 55 | visited: Set<string> |
| 56 | ): Promise<{ type: "commit" | "tree" | "blob" | "tag"; payload: Uint8Array } | undefined> { |
| 57 | const visitKey = `${location.pack.packKey}#${location.objectIndex}`; |
| 58 | if (visited.has(visitKey)) throw new Error("pack object recursion cycle"); |
| 59 | visited.add(visitKey); |
| 60 | try { |
| 61 | const entryLength = location.nextOffset - location.offset; |
| 62 | if (entryLength <= 0) return undefined; |
| 63 | const entry = await readPackRange(env, location.pack.packKey, location.offset, entryLength); |
| 64 | if (!entry) return undefined; |
| 65 | |
| 66 | const header = readPackHeaderExFromBuf(entry, 0); |
| 67 | if (!header) return undefined; |
| 68 | const inflated = await inflate(entry.subarray(header.headerLen)); |
| 69 | |
| 70 | const baseType = typeCodeToObjectType(header.type); |
| 71 | if (baseType) return { type: baseType, payload: inflated }; |
| 72 | |
| 73 | if (header.type === 6) { |
| 74 | const baseOffset = location.offset - (header.baseRel || 0); |
| 75 | const baseIndex = findOffsetIndex(location.idx, baseOffset); |
| 76 | if (baseIndex === undefined) return undefined; |
| 77 | const baseNextOffset = getNextOffsetByIndex(location.idx, baseIndex); |
| 78 | if (baseNextOffset === undefined) return undefined; |
| 79 | const base = await readPackedObjectFromLocation( |
| 80 | env, |
| 81 | { |
| 82 | pack: location.pack, |
| 83 | idx: location.idx, |
| 84 | objectIndex: baseIndex, |
| 85 | offset: baseOffset, |
| 86 | nextOffset: baseNextOffset, |
| 87 | oid: getOidHexAt(location.idx, baseIndex), |
| 88 | }, |
| 89 | rows, |
| 90 | visited |
| 91 | ); |
| 92 | if (!base) return undefined; |
| 93 | return { |
| 94 | type: base.type, |
| 95 | payload: applyGitDelta(base.payload, inflated), |
| 96 | }; |
| 97 | } |
| 98 | |
| 99 | if (header.type === 7 && header.baseOid) { |
| 100 | const base = await readPackedObjectFromCatalogRows(env, rows, header.baseOid, visited); |
| 101 | if (!base) return undefined; |
| 102 | return { |
| 103 | type: base.type, |
| 104 | payload: applyGitDelta(base.payload, inflated), |
| 105 | }; |
| 106 | } |
| 107 | |
| 108 | return undefined; |
| 109 | } finally { |
| 110 | visited.delete(visitKey); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | export async function readPackedObjectFromCatalogRows( |
| 115 | env: Env, |
| 116 | rows: PackCatalogRow[], |
| 117 | oid: string, |
| 118 | visited: Set<string> = new Set<string>() |
| 119 | ): Promise<{ type: "commit" | "tree" | "blob" | "tag"; payload: Uint8Array } | undefined> { |
| 120 | const location = await findCatalogObject(env, rows, oid); |
| 121 | if (!location) return undefined; |
| 122 | return await readPackedObjectFromLocation(env, location, rows, visited); |
| 123 | } |
| 124 | |
| 125 | export async function findCatalogPacksContainingOid( |
| 126 | env: Env, |
| 127 | rows: PackCatalogRow[], |
| 128 | oid: string |
| 129 | ): Promise<string[]> { |
| 130 | const oidLc = oid.toLowerCase(); |
| 131 | const matches: string[] = []; |
| 132 | for (const row of rows) { |
| 133 | const idx = await loadIdxView(env, row.packKey, undefined, row.packBytes); |
| 134 | if (!idx) continue; |
| 135 | if (findOidIndex(idx, oidLc) >= 0) matches.push(row.packKey); |
| 136 | } |
| 137 | return matches; |
| 138 | } |