Merge remote-tracking branch 'origin/farm/da40d318/fix-plugin-install-sha-shallow-clone'
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
|
||||
- Removed `/shake summary`, the `shake-summary` auto-compaction strategy, and the `providers.shakeSummaryModel` setting. Use `/shake` or `compaction.strategy: shake` for mechanical artifact-backed elision without local-model CPU.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed plugin install failing for sources pinned to a SHA: `git.clone()` no longer adds `--depth 1` when `options.sha` is set, so the checkout of arbitrary commits succeeds instead of bailing out with "shallow clone may not contain this commit" ([#1589](https://github.com/can1357/oh-my-pi/issues/1589)).
|
||||
|
||||
## [15.7.3] - 2026-05-31
|
||||
### Added
|
||||
|
||||
|
||||
@@ -1261,9 +1261,15 @@ export async function clone(url: string, targetDir: string, options: CloneOption
|
||||
const absoluteTarget = path.resolve(targetDir);
|
||||
await fs.promises.mkdir(path.dirname(absoluteTarget), { recursive: true });
|
||||
|
||||
const args = ["clone", "--depth", "1"];
|
||||
// `git clone --depth 1 --single-branch` only fetches the tip of the target
|
||||
// branch, so any subsequent `git checkout <sha>` for a non-tip commit fails
|
||||
// with "reference is not a tree". When the caller pinned a specific SHA we
|
||||
// fall back to a full clone so the object is guaranteed to be present.
|
||||
const shallow = !options.sha;
|
||||
const args = ["clone"];
|
||||
if (shallow) args.push("--depth", "1");
|
||||
if (options.ref) args.push("--branch", options.ref, "--single-branch");
|
||||
else args.push("--single-branch");
|
||||
else if (shallow) args.push("--single-branch");
|
||||
args.push(url, absoluteTarget);
|
||||
|
||||
try {
|
||||
@@ -1273,7 +1279,7 @@ export async function clone(url: string, targetDir: string, options: CloneOption
|
||||
await checkout(absoluteTarget, options.sha, options.signal);
|
||||
} catch {
|
||||
await fs.promises.rm(absoluteTarget, { force: true, recursive: true });
|
||||
throw new Error(`Failed to checkout SHA ${options.sha} - shallow clone may not contain this commit`);
|
||||
throw new Error(`Failed to checkout SHA ${options.sha} in cloned repository ${url}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
|
||||
import * as git from "@oh-my-pi/pi-coding-agent/utils/git";
|
||||
|
||||
// Regression coverage for #1589: `git.clone({ sha })` used to hardcode
|
||||
// `--depth 1`, producing a shallow clone whose object store never contained
|
||||
// non-tip commits. The subsequent `git checkout <sha>` then failed with
|
||||
// "shallow clone may not contain this commit".
|
||||
|
||||
const GIT_ENV = {
|
||||
GIT_AUTHOR_NAME: "t",
|
||||
GIT_AUTHOR_EMAIL: "t@example.com",
|
||||
GIT_COMMITTER_NAME: "t",
|
||||
GIT_COMMITTER_EMAIL: "t@example.com",
|
||||
} as const;
|
||||
|
||||
function gitRun(cwd: string, args: string[]): string {
|
||||
const result = Bun.spawnSync({
|
||||
cmd: ["git", ...args],
|
||||
cwd,
|
||||
env: { ...process.env, ...GIT_ENV },
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(`git ${args.join(" ")} failed: ${result.stderr.toString()}`);
|
||||
}
|
||||
return result.stdout.toString().trim();
|
||||
}
|
||||
|
||||
describe("git.clone with options.sha", () => {
|
||||
let tmpRoot: string;
|
||||
let upstreamUrl: string;
|
||||
let firstSha: string;
|
||||
let tipSha: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "omp-git-clone-test-"));
|
||||
const upstream = path.join(tmpRoot, "upstream");
|
||||
await fs.mkdir(upstream, { recursive: true });
|
||||
|
||||
// `file://` is required: local-path clones ignore `--depth`, which would
|
||||
// mask the bug. See git-clone(1) "GIT URLS" / "LOCAL PROTOCOL".
|
||||
upstreamUrl = `file://${upstream}`;
|
||||
|
||||
gitRun(upstream, ["init", "-q", "-b", "main"]);
|
||||
gitRun(upstream, ["commit", "-q", "--allow-empty", "-m", "first"]);
|
||||
firstSha = gitRun(upstream, ["rev-parse", "HEAD"]);
|
||||
gitRun(upstream, ["commit", "-q", "--allow-empty", "-m", "second"]);
|
||||
gitRun(upstream, ["commit", "-q", "--allow-empty", "-m", "third"]);
|
||||
tipSha = gitRun(upstream, ["rev-parse", "HEAD"]);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(tmpRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("checks out a non-tip SHA (regression for #1589)", async () => {
|
||||
const target = path.join(tmpRoot, "clone-non-tip");
|
||||
await git.clone(upstreamUrl, target, { sha: firstSha });
|
||||
expect(gitRun(target, ["rev-parse", "HEAD"])).toBe(firstSha);
|
||||
});
|
||||
|
||||
test("still succeeds when SHA happens to be the tip", async () => {
|
||||
const target = path.join(tmpRoot, "clone-tip");
|
||||
await git.clone(upstreamUrl, target, { sha: tipSha });
|
||||
expect(gitRun(target, ["rev-parse", "HEAD"])).toBe(tipSha);
|
||||
});
|
||||
|
||||
test("cleans up the target directory when SHA does not exist", async () => {
|
||||
const target = path.join(tmpRoot, "clone-missing");
|
||||
await expect(git.clone(upstreamUrl, target, { sha: "0".repeat(40) })).rejects.toThrow(/Failed to checkout SHA/);
|
||||
await expect(fs.stat(target)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user