fix: bulk windows apply-patch updates
This commit is contained in:
@@ -63,6 +63,8 @@ export interface ApplyPatchV2FileSystem {
|
||||
readBlock(path: string, blockIndex: number, blockBytes: number): Promise<Buffer>;
|
||||
writeFile(path: string, content: Buffer): Promise<void>;
|
||||
deleteFile(path: string): Promise<void>;
|
||||
readFiles?(paths: string[]): Promise<Map<string, string>>;
|
||||
applyReplacementsBulk?(paths: Iterable<string>, plans: Map<string, ApplyPatchV2BulkReplacementWritePlan>): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ApplyPatchV2FileStat {
|
||||
@@ -114,15 +116,17 @@ export class ApplyPatchV2Error extends Error {
|
||||
|
||||
type PlannedFileState = { exists: true; content: string } | { exists: false; content: "" };
|
||||
type PlannedOperation = { kind: "write"; path: string; content: string } | { kind: "delete"; path: string };
|
||||
type Replacement = [start: number, oldLength: number, newLines: string[]];
|
||||
interface BulkReplacementWritePlan {
|
||||
export type ApplyPatchV2Replacement = [start: number, oldLength: number, newLines: string[]];
|
||||
type Replacement = ApplyPatchV2Replacement;
|
||||
export interface ApplyPatchV2BulkReplacementWritePlan {
|
||||
path: string;
|
||||
originalBytes: number;
|
||||
originalSha256: string;
|
||||
finalBytes: number;
|
||||
finalSha256: string;
|
||||
replacements: Replacement[];
|
||||
replacements: ApplyPatchV2Replacement[];
|
||||
}
|
||||
type BulkReplacementWritePlan = ApplyPatchV2BulkReplacementWritePlan;
|
||||
|
||||
interface ApplyPatchV2Plan {
|
||||
changed: string[];
|
||||
@@ -432,12 +436,19 @@ function instrumentApplyPatchV2Executor(executor: ApplyPatchV2Executor, metrics:
|
||||
}
|
||||
|
||||
function instrumentApplyPatchV2FileSystem(fs: ApplyPatchV2FileSystem, metrics: ApplyPatchV2RemoteMetrics): ApplyPatchV2FileSystem {
|
||||
return {
|
||||
const instrumented: ApplyPatchV2FileSystem = {
|
||||
stat: (path) => recordApplyPatchV2FsOperation(metrics, "fs.stat", () => fs.stat(path)),
|
||||
readBlock: (path, blockIndex, blockBytes) => recordApplyPatchV2FsOperation(metrics, "fs.readBlock", () => fs.readBlock(path, blockIndex, blockBytes)),
|
||||
writeFile: (path, content) => recordApplyPatchV2FsOperation(metrics, "fs.writeFile", () => fs.writeFile(path, content)),
|
||||
deleteFile: (path) => recordApplyPatchV2FsOperation(metrics, "fs.deleteFile", () => fs.deleteFile(path)),
|
||||
};
|
||||
if (fs.readFiles !== undefined) {
|
||||
instrumented.readFiles = (paths) => recordApplyPatchV2FsOperation(metrics, "fs.readFiles", () => fs.readFiles!(paths));
|
||||
}
|
||||
if (fs.applyReplacementsBulk !== undefined) {
|
||||
instrumented.applyReplacementsBulk = (paths, plans) => recordApplyPatchV2FsOperation(metrics, "fs.applyReplacementsBulk", () => fs.applyReplacementsBulk!(paths, plans));
|
||||
}
|
||||
return instrumented;
|
||||
}
|
||||
|
||||
async function recordApplyPatchV2FsOperation<T>(metrics: ApplyPatchV2RemoteMetrics, operation: string, run: () => Promise<T>): Promise<T> {
|
||||
@@ -621,7 +632,11 @@ function isBulkReadFailure(error: unknown): boolean {
|
||||
}
|
||||
|
||||
function shouldUseBulkUpdatePath(executor: ApplyPatchV2Executor, hunks: PatchHunk[]): boolean {
|
||||
if (!executor.run || executor.fs !== undefined) return false;
|
||||
if (executor.fs !== undefined) {
|
||||
if (executor.fs.readFiles === undefined || executor.fs.applyReplacementsBulk === undefined) return false;
|
||||
} else if (!executor.run) {
|
||||
return false;
|
||||
}
|
||||
const paths = new Set<string>();
|
||||
for (const hunk of hunks) {
|
||||
if (hunk.kind !== "update" || hunk.movePath !== null) return false;
|
||||
@@ -926,13 +941,20 @@ async function readRemoteText(executor: ApplyPatchV2Executor, target: string): P
|
||||
|
||||
async function readRemoteTextsBulk(executor: ApplyPatchV2Executor, targets: string[]): Promise<Map<string, string>> {
|
||||
if (targets.length === 0) return new Map();
|
||||
if (executor.fs?.readFiles !== undefined && targets.length > 1) {
|
||||
const files = await executor.fs.readFiles(targets);
|
||||
for (const target of targets) {
|
||||
if (!files.has(target)) throw new ApplyPatchV2Error("remote apply-patch v2 fs bulk read omitted target", { target });
|
||||
}
|
||||
return files;
|
||||
}
|
||||
if (executor.fs || targets.length === 1) {
|
||||
const files = new Map<string, string>();
|
||||
for (const target of targets) files.set(target, await readRemoteText(executor, target));
|
||||
return files;
|
||||
}
|
||||
const result = await checkedRemoteV2(executor, "read-bulk-b64", targets);
|
||||
return decodeRemoteBulkRead(result.stdout, targets);
|
||||
return decodeApplyPatchV2BulkRead(result.stdout, targets);
|
||||
}
|
||||
|
||||
function decodeRemoteReadBlock(stdout: string, target: string, blockIndex: number, expectedChunkBytes: number): Buffer {
|
||||
@@ -979,7 +1001,7 @@ function decodeRemoteReadBlock(stdout: string, target: string, blockIndex: numbe
|
||||
return decoded.length > expectedChunkBytes ? decoded.subarray(0, expectedChunkBytes) : decoded;
|
||||
}
|
||||
|
||||
function decodeRemoteBulkRead(stdout: string, targets: string[]): Map<string, string> {
|
||||
export function decodeApplyPatchV2BulkRead(stdout: string, targets: string[]): Map<string, string> {
|
||||
const lines = stdout.split(/\r?\n/u);
|
||||
const markerIndex = lines.findIndex((line) => line.startsWith(`${remoteBulkReadMarker} `));
|
||||
if (markerIndex < 0) {
|
||||
@@ -1050,8 +1072,17 @@ async function writeRemoteText(executor: ApplyPatchV2Executor, target: string, c
|
||||
}
|
||||
|
||||
async function writeRemoteReplacementsBulk(executor: ApplyPatchV2Executor, paths: Iterable<string>, plans: Map<string, BulkReplacementWritePlan>): Promise<void> {
|
||||
const targets = Array.from(paths);
|
||||
const { targets, payload } = formatApplyPatchV2BulkReplacementPayload(paths, plans);
|
||||
if (targets.length === 0) return;
|
||||
if (executor.fs?.applyReplacementsBulk !== undefined) {
|
||||
await executor.fs.applyReplacementsBulk(targets, plans);
|
||||
return;
|
||||
}
|
||||
await checkedRemoteV2(executor, "apply-replacements-bulk-stdin", [String(targets.length)], payload);
|
||||
}
|
||||
|
||||
export function formatApplyPatchV2BulkReplacementPayload(paths: Iterable<string>, plans: Map<string, ApplyPatchV2BulkReplacementWritePlan>): { targets: string[]; payload: string } {
|
||||
const targets = Array.from(paths);
|
||||
const records: string[] = [];
|
||||
for (const target of targets) {
|
||||
const plan = plans.get(target);
|
||||
@@ -1073,7 +1104,7 @@ async function writeRemoteReplacementsBulk(executor: ApplyPatchV2Executor, paths
|
||||
replacementFields.join(";"),
|
||||
].join(" "));
|
||||
}
|
||||
await checkedRemoteV2(executor, "apply-replacements-bulk-stdin", [String(targets.length)], `${records.join("\n")}\n`);
|
||||
return { targets, payload: `${records.join("\n")}\n` };
|
||||
}
|
||||
|
||||
type RemoteV2Operation =
|
||||
|
||||
Reference in New Issue
Block a user