mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 14:01:06 +00:00
fix(coding-agent): refresh temporary git extension caches on cache hits
This commit is contained in:
parent
92fdb53c10
commit
310da43042
3 changed files with 101 additions and 2 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed temporary git package caches (`-e <git-url>`) to refresh on cache hits for unpinned sources, including detached/no-upstream checkouts
|
||||
|
||||
## [0.52.7] - 2026-02-06
|
||||
|
||||
### New Features
|
||||
|
|
|
|||
|
|
@ -870,6 +870,8 @@ export class DefaultPackageManager implements PackageManager {
|
|||
if (!existsSync(installedPath)) {
|
||||
const installed = await installMissing();
|
||||
if (!installed) continue;
|
||||
} else if (scope === "temporary" && !parsed.pinned) {
|
||||
await this.refreshTemporaryGitSource(parsed, sourceStr);
|
||||
}
|
||||
metadata.baseDir = installedPath;
|
||||
this.collectPackageResources(installedPath, accumulator, filter, metadata);
|
||||
|
|
@ -1153,8 +1155,13 @@ export class DefaultPackageManager implements PackageManager {
|
|||
// Fetch latest from remote (handles force-push by getting new history)
|
||||
await this.runCommand("git", ["fetch", "--prune", "origin"], { cwd: targetDir });
|
||||
|
||||
// Reset to upstream tracking branch (handles force-push gracefully)
|
||||
await this.runCommand("git", ["reset", "--hard", "@{upstream}"], { cwd: targetDir });
|
||||
// Reset to tracking branch. Fall back to origin/HEAD when no upstream is configured.
|
||||
try {
|
||||
await this.runCommand("git", ["reset", "--hard", "@{upstream}"], { cwd: targetDir });
|
||||
} catch {
|
||||
await this.runCommand("git", ["remote", "set-head", "origin", "-a"], { cwd: targetDir }).catch(() => {});
|
||||
await this.runCommand("git", ["reset", "--hard", "origin/HEAD"], { cwd: targetDir });
|
||||
}
|
||||
|
||||
// Clean untracked files (extensions should be pristine)
|
||||
await this.runCommand("git", ["clean", "-fdx"], { cwd: targetDir });
|
||||
|
|
@ -1165,6 +1172,16 @@ export class DefaultPackageManager implements PackageManager {
|
|||
}
|
||||
}
|
||||
|
||||
private async refreshTemporaryGitSource(source: GitSource, sourceStr: string): Promise<void> {
|
||||
try {
|
||||
await this.withProgress("pull", sourceStr, `Refreshing ${sourceStr}...`, async () => {
|
||||
await this.updateGit(source, "temporary");
|
||||
});
|
||||
} catch {
|
||||
// Keep cached temporary checkout if refresh fails.
|
||||
}
|
||||
}
|
||||
|
||||
private async removeGit(source: GitSource, scope: SourceScope): Promise<void> {
|
||||
const targetDir = this.getGitInstallPath(source, scope);
|
||||
if (!existsSync(targetDir)) return;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -132,6 +133,20 @@ describe("DefaultPackageManager git update", () => {
|
|||
expect(getCurrentCommit(installedDir)).toBe(latestCommit);
|
||||
expect(getFileContent(installedDir, "extension.ts")).toBe("// v4");
|
||||
});
|
||||
|
||||
it("should update even when local checkout has no upstream", async () => {
|
||||
setupRemoteAndInstall();
|
||||
createCommit(remoteDir, "extension.ts", "// v2", "Second commit");
|
||||
const latestCommit = createCommit(remoteDir, "extension.ts", "// v3", "Third commit");
|
||||
|
||||
const detachedCommit = getCurrentCommit(installedDir);
|
||||
git(["checkout", detachedCommit], installedDir);
|
||||
|
||||
await packageManager.update();
|
||||
|
||||
expect(getCurrentCommit(installedDir)).toBe(latestCommit);
|
||||
expect(getFileContent(installedDir, "extension.ts")).toBe("// v3");
|
||||
});
|
||||
});
|
||||
|
||||
describe("force-push scenarios", () => {
|
||||
|
|
@ -233,6 +248,69 @@ describe("DefaultPackageManager git update", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("temporary git sources", () => {
|
||||
it("should refresh cached temporary git sources when resolving", async () => {
|
||||
const gitHost = "github.com";
|
||||
const gitPath = "test/extension";
|
||||
const hash = createHash("sha256").update(`git-${gitHost}-${gitPath}`).digest("hex").slice(0, 8);
|
||||
const cachedDir = join(tmpdir(), "pi-extensions", `git-${gitHost}`, hash, gitPath);
|
||||
const extensionFile = join(cachedDir, "pi-extensions", "session-breakdown.ts");
|
||||
|
||||
rmSync(cachedDir, { recursive: true, force: true });
|
||||
mkdirSync(join(cachedDir, "pi-extensions"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(cachedDir, "package.json"),
|
||||
JSON.stringify({ pi: { extensions: ["./pi-extensions"] } }, null, 2),
|
||||
);
|
||||
writeFileSync(extensionFile, "// stale");
|
||||
|
||||
const executedCommands: string[] = [];
|
||||
const managerWithInternals = packageManager as unknown as {
|
||||
runCommand: (command: string, args: string[], options?: { cwd?: string }) => Promise<void>;
|
||||
};
|
||||
managerWithInternals.runCommand = async (command, args) => {
|
||||
executedCommands.push(`${command} ${args.join(" ")}`);
|
||||
if (command === "git" && args[0] === "reset") {
|
||||
writeFileSync(extensionFile, "// fresh");
|
||||
}
|
||||
};
|
||||
|
||||
await packageManager.resolveExtensionSources([gitSource], { temporary: true });
|
||||
|
||||
expect(executedCommands).toContain("git fetch --prune origin");
|
||||
expect(getFileContent(cachedDir, "pi-extensions/session-breakdown.ts")).toBe("// fresh");
|
||||
});
|
||||
|
||||
it("should not refresh pinned temporary git sources", async () => {
|
||||
const gitHost = "github.com";
|
||||
const gitPath = "test/extension";
|
||||
const hash = createHash("sha256").update(`git-${gitHost}-${gitPath}`).digest("hex").slice(0, 8);
|
||||
const cachedDir = join(tmpdir(), "pi-extensions", `git-${gitHost}`, hash, gitPath);
|
||||
const extensionFile = join(cachedDir, "pi-extensions", "session-breakdown.ts");
|
||||
|
||||
rmSync(cachedDir, { recursive: true, force: true });
|
||||
mkdirSync(join(cachedDir, "pi-extensions"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(cachedDir, "package.json"),
|
||||
JSON.stringify({ pi: { extensions: ["./pi-extensions"] } }, null, 2),
|
||||
);
|
||||
writeFileSync(extensionFile, "// pinned");
|
||||
|
||||
const executedCommands: string[] = [];
|
||||
const managerWithInternals = packageManager as unknown as {
|
||||
runCommand: (command: string, args: string[], options?: { cwd?: string }) => Promise<void>;
|
||||
};
|
||||
managerWithInternals.runCommand = async (command, args) => {
|
||||
executedCommands.push(`${command} ${args.join(" ")}`);
|
||||
};
|
||||
|
||||
await packageManager.resolveExtensionSources([`${gitSource}@main`], { temporary: true });
|
||||
|
||||
expect(executedCommands).toEqual([]);
|
||||
expect(getFileContent(cachedDir, "pi-extensions/session-breakdown.ts")).toBe("// pinned");
|
||||
});
|
||||
});
|
||||
|
||||
describe("scope-aware update", () => {
|
||||
it("should not install locally when source is only registered globally", async () => {
|
||||
setupRemoteAndInstall();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue