fix(release): handle large preparation diffs (#113760)

This commit is contained in:
Peter Steinberger
2026-07-25 10:15:47 -07:00
committed by GitHub
parent f6e8446e55
commit be95b78ae6
2 changed files with 33 additions and 1 deletions

View File

@@ -40,6 +40,7 @@ type WorktreeState = {
const DEFAULT_JOBS = 4;
const MAX_JOBS = 16;
const GIT_OUTPUT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
export function parseReleasePrepareArgs(argv: string[]): ReleasePrepareArgs {
let android = false;
@@ -298,7 +299,7 @@ export function runReleasePrepareStep(
return result.status ?? 1;
}
function readWorktreeState(rootDir: string): WorktreeState {
export function readWorktreeState(rootDir: string): WorktreeState {
const head = git(rootDir, ["rev-parse", "HEAD"]);
const status = git(rootDir, ["status", "--porcelain=v1", "--untracked-files=all"]);
const diff = git(rootDir, ["diff", "--binary", "HEAD"]);
@@ -334,6 +335,9 @@ function git(cwd: string, args: string[]): string {
cwd,
encoding: "utf8",
env: process.env,
// Version preparation can legitimately create multi-megabyte generated diffs.
// Keep the fingerprint capture bounded without inheriting Node's 1 MiB default.
maxBuffer: GIT_OUTPUT_MAX_BUFFER_BYTES,
stdio: ["ignore", "pipe", "pipe"],
});
if (result.status !== 0) {

View File

@@ -1,3 +1,7 @@
import { execFileSync } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
// Release prepare tests cover shadow planning, cutover commands, and candidate manifests.
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it, vi } from "vitest";
@@ -5,6 +9,7 @@ import {
buildReleasePreparationManifest,
createReleasePrepareSteps,
parseReleasePrepareArgs,
readWorktreeState,
runReleasePrepareStep,
runReleasePrepareSteps,
} from "../../scripts/release-prepare.ts";
@@ -159,6 +164,29 @@ describe("release preparation plan", () => {
});
describe("release preparation manifest", () => {
it("fingerprints generated diffs larger than Node's default child buffer", () => {
const rootDir = mkdtempSync(path.join(tmpdir(), "openclaw-release-prepare-"));
try {
execFileSync("git", ["init", "-q"], { cwd: rootDir });
execFileSync("git", ["config", "user.email", "release-test@openclaw.invalid"], {
cwd: rootDir,
});
execFileSync("git", ["config", "user.name", "OpenClaw Release Test"], { cwd: rootDir });
writeFileSync(path.join(rootDir, "package.json"), '{"version":"2026.7.2"}\n');
writeFileSync(path.join(rootDir, "generated.txt"), `${"a".repeat(2 * 1024 * 1024)}\n`);
execFileSync("git", ["add", "."], { cwd: rootDir });
execFileSync("git", ["commit", "-q", "-m", "test fixture"], { cwd: rootDir });
writeFileSync(path.join(rootDir, "generated.txt"), `${"b".repeat(2 * 1024 * 1024)}\n`);
const state = readWorktreeState(rootDir);
expect(state.changedFiles).toEqual(["generated.txt"]);
expect(state.fingerprint).toMatch(/^[0-9a-f]{64}$/u);
} finally {
rmSync(rootDir, { force: true, recursive: true });
}
});
it("binds the plan to the exact source and worktree fingerprint", () => {
const steps = runReleasePrepareSteps({
cwd: "/repo",