Skip to content
File

Blob: src/worker/git/pack/indexer/resolve/helpers.ts

typescript141 lines
1import { computeOidBytes, objTypeCode } from "@/worker/git/core/objects";
2import { applyGitDelta } from "@/worker/git/object-store/delta";
3 
4import type { PackEntryTable, ResolveOptions } from "../types";
5 
6import { typeCodeToObjectType } from "@/worker/git/object-store/support";
7import { getBasePayload } from "./materialize";
8import { promoteReadyInPackDependents, type InPackDependencyQueue } from "./dependencies";
9import { throwIfAborted } from "./errors";
10import type { PayloadLRU } from "./payloadCache";
11import { inflateFromReader, type SequentialReader } from "./reader";
12import { promoteWaitingRefDeltas, type RefBaseLookup } from "./refLookup";
13 
14export function storeOid(table: PackEntryTable, index: number, oidBytes: Uint8Array): void {
15 table.oids.set(oidBytes, index * 20);
16 table.resolved[index] = 1;
17}
18 
19interface ResolveDeltaEntryArgs {
20 index: number;
21 resolveOpts: ResolveOptions;
22 table: PackEntryTable;
23 lru: PayloadLRU;
24 deltaReader: SequentialReader;
25 baseReader: SequentialReader;
26 baseIndex: Int32Array;
27 resolvedTypeCodes: Uint8Array;
28 isBase: Uint8Array;
29 deadlines: Uint32Array;
30 refLookup: RefBaseLookup | null;
31 dependencyQueue?: InPackDependencyQueue;
32 readyDeferred?: number[];
33 deferredQueued?: Uint8Array;
34}
35 
36/**
37 * Resolve a delta whose base is already known to be available. The base may be
38 * in-pack or external; `getBasePayload()` hides that distinction.
39 */
40export async function resolveDeltaEntry(args: ResolveDeltaEntryArgs): Promise<void> {
41 throwIfAborted(args.resolveOpts.signal, args.resolveOpts.log, "resolve:delta-entry");
42 const bi = args.baseIndex[args.index];
43 if (bi < 0 || !args.table.resolved[bi]) {
44 throw new Error(`resolve: base entry ${bi} is not ready for ${args.index}`);
45 }
46 
47 if (tryResolveRefsOnlyBlobDelta(args, bi)) return;
48 
49 const base = await getBasePayload(
50 args.resolveOpts,
51 bi,
52 args.lru,
53 args.baseReader,
54 args.table,
55 args.baseIndex
56 );
57 const deltaPayload = await inflateFromReader(args.deltaReader, args.table, args.index);
58 throwIfAborted(args.resolveOpts.signal, args.resolveOpts.log, "resolve:delta-apply");
59 const result = applyGitDelta(base.payload, deltaPayload);
60 if (result.length !== args.table.decompressedSizes[args.index]) {
61 throw new Error(
62 `resolve: delta result size mismatch at offset ${args.table.offsets[args.index]} (expected ${args.table.decompressedSizes[args.index]}, got ${result.length})`
63 );
64 }
65 
66 if (args.resolveOpts.existingIdxView) {
67 args.table.resolved[args.index] = 1;
68 } else {
69 storeOid(args.table, args.index, await computeOidBytes(base.type, result));
70 }
71 args.resolvedTypeCodes[args.index] = objTypeCode(base.type);
72 args.table.objectTypes[args.index] = args.resolvedTypeCodes[args.index];
73 args.resolveOpts.scanResult.refsBuilder?.recordObject(args.index, base.type, result);
74 if (args.refLookup) {
75 promoteWaitingRefDeltas(
76 args.refLookup,
77 args.index,
78 args.table,
79 args.baseIndex,
80 args.isBase,
81 args.deadlines,
82 args.readyDeferred,
83 args.deferredQueued
84 );
85 }
86 if (args.dependencyQueue && args.readyDeferred && args.deferredQueued) {
87 // OFS_DELTA dependents can already know their base entry index even when
88 // that base is still unresolved. Wake them here now that this entry's OID
89 // and payload are available, or they remain stranded in the deferred set.
90 promoteReadyInPackDependents(
91 args.dependencyQueue,
92 args.index,
93 args.readyDeferred,
94 args.deferredQueued,
95 args.table,
96 args.baseIndex
97 );
98 }
99 if (args.isBase[args.index]) {
100 args.lru.set(args.index, { type: base.type, payload: result });
101 }
102}
103 
104function tryResolveRefsOnlyBlobDelta(args: ResolveDeltaEntryArgs, baseIndex: number): boolean {
105 if (!args.resolveOpts.existingIdxView) return false;
106 
107 const baseType = typeCodeToObjectType(args.resolvedTypeCodes[baseIndex]);
108 if (baseType !== "blob") return false;
109 
110 // `.idx` already carries the final OID for refs-only backfill, and blob
111 // objects have no logical closure edges. Avoid inflating and applying blob
112 // delta chains just to record an empty ref list.
113 args.table.resolved[args.index] = 1;
114 args.resolvedTypeCodes[args.index] = args.resolvedTypeCodes[baseIndex];
115 args.table.objectTypes[args.index] = args.resolvedTypeCodes[args.index];
116 args.resolveOpts.scanResult.refsBuilder?.recordBlob(args.index);
117 if (args.refLookup) {
118 promoteWaitingRefDeltas(
119 args.refLookup,
120 args.index,
121 args.table,
122 args.baseIndex,
123 args.isBase,
124 args.deadlines,
125 args.readyDeferred,
126 args.deferredQueued
127 );
128 }
129 if (args.dependencyQueue && args.readyDeferred && args.deferredQueued) {
130 promoteReadyInPackDependents(
131 args.dependencyQueue,
132 args.index,
133 args.readyDeferred,
134 args.deferredQueued,
135 args.table,
136 args.baseIndex
137 );
138 }
139 return true;
140}