mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 10:55:31 -06:00
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:
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user