fix(infra): preserve git metadata across short reads (#109419)

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: ZengWen-DT <ceng.wen@xydigit.com>
This commit is contained in:
wahaha1223
2026-07-23 05:49:53 +08:00
committed by GitHub
parent 4501da6123
commit 2287d65cda
2 changed files with 73 additions and 2 deletions
+71 -1
View File
@@ -1,4 +1,5 @@
// Covers git commit helper behavior in fake repositories.
import fsSync from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import process from "node:process";
@@ -56,6 +57,23 @@ async function makeFakeOpenClawPackage(root: string) {
await fs.writeFile(path.join(root, "package.json"), JSON.stringify({ name: "openclaw" }));
}
function limitPositionalReads(maxBytes: number) {
const realReadSync = fsSync.readSync.bind(fsSync);
let totalBytesRead = 0;
vi.spyOn(fsSync, "readSync").mockImplementation(((
fd: number,
buffer: NodeJS.ArrayBufferView,
offset: number,
length: number,
position: number | null,
) => {
const bytesRead = realReadSync(fd, buffer, offset, Math.min(length, maxBytes), position);
totalBytesRead += bytesRead;
return bytesRead;
}) as typeof fsSync.readSync);
return () => totalBytesRead;
}
describe("git commit resolution", () => {
let resolveCommitHash: (typeof import("./git-commit.js"))["resolveCommitHash"];
@@ -210,7 +228,7 @@ describe("git commit resolution", () => {
expect(resolveCommitHash({ cwd: repoA, env: {} })).toBe("0123456");
});
it("reads packed refs from the common git dir for worktree-style checkouts", async () => {
it("reads packed refs after short commondir reads in worktree-style checkouts", async () => {
const temp = await makeTempDir("git-commit-packed-refs");
const checkoutRoot = path.join(temp, "checkout");
const commonGitDir = path.join(temp, "git-common");
@@ -224,6 +242,7 @@ describe("git commit resolution", () => {
"refs/heads/main": "0123456789abcdef0123456789abcdef01234567",
},
});
limitPositionalReads(4);
expect(resolveCommitHash({ cwd: checkoutRoot, env: {} })).toBe("0123456");
});
@@ -381,6 +400,57 @@ describe("git commit resolution", () => {
expect(resolveCommitHash({ cwd: repoRootValue, env: {} })).toBe("bbbbbbb");
});
it("fills short positional reads for loose refs", async () => {
const temp = await makeTempDir("git-commit-short-ref");
const repoRoot = path.join(temp, "repo");
await makeFakeGitRepo(repoRoot, {
head: "ref: refs/heads/main\n",
refs: {
"refs/heads/main": "abcdef0123456789abcdef0123456789abcdef01",
},
});
limitPositionalReads(4);
expect(resolveCommitHash({ cwd: repoRoot, env: {} })).toBe("abcdef0");
});
it("keeps short-read retries within the bounded metadata window", async () => {
const temp = await makeTempDir("git-commit-bounded-ref");
const repoRoot = path.join(temp, "repo");
await makeFakeGitRepo(repoRoot, {
head: "ref: refs/heads/main\n",
refs: {
"refs/heads/main": `${"x".repeat(256)}abcdef0123456789`,
},
});
const totalBytesRead = limitPositionalReads(4);
expect(resolveCommitHash({ cwd: repoRoot, env: {} })).toBeNull();
expect(totalBytesRead()).toBe(256);
});
it("falls back to baked metadata when a bounded Git metadata read errors", async () => {
const temp = await makeTempDir("git-commit-read-error");
const repoRoot = path.join(temp, "repo");
await makeFakeGitRepo(repoRoot, {
head: "ref: refs/heads/main\n",
refs: {
"refs/heads/main": "abcdef0123456789abcdef0123456789abcdef01",
},
});
vi.spyOn(fsSync, "readSync").mockImplementationOnce((() => {
throw Object.assign(new Error("EIO: forced read failure"), { code: "EIO" });
}) as typeof fsSync.readSync);
expect(
resolveCommitHash({
cwd: repoRoot,
env: {},
readers: { readBuildInfoCommit: () => "deadbee" },
}),
).toBe("deadbee");
});
it("reads full HEAD refs before parsing long branch names", async () => {
const temp = await makeTempDir("git-commit-long-head");
const repoRootLocal = path.join(temp, "repo");
+2 -1
View File
@@ -4,6 +4,7 @@ import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
import { readFileWindowFullySync } from "./file-read.js";
import { resolveGitHeadPath } from "./git-root.js";
import { pruneMapToMaxSize } from "./map-size.js";
import { resolveOpenClawPackageRootSync } from "./openclaw-root.js";
@@ -59,7 +60,7 @@ const safeReadFilePrefix = (filePath: string, limit = 256) => {
const fd = fs.openSync(filePath, "r");
try {
const buf = Buffer.alloc(limit);
const bytesRead = fs.readSync(fd, buf, 0, limit, 0);
const bytesRead = readFileWindowFullySync(fd, buf, 0);
return buf.subarray(0, bytesRead).toString("utf-8");
} finally {
fs.closeSync(fd);