@@ -11,6 +11,10 @@
|
||||
- Built-in tools now work like custom tools in extensions. To get built-in tool definitions, import `readToolDefinition` / `createReadToolDefinition()` and the equivalent `bash`, `edit`, `write`, `grep`, `find`, and `ls` exports from `@mariozechner/pi-coding-agent`.
|
||||
- Cleaned up `buildSystemPrompt()` so built-in tool snippets and tool-local guidelines come from built-in `ToolDefinition` metadata, while cross-tool and global prompt rules stay in system prompt construction.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `pi update` for git packages to skip destructive reset, clean, and reinstall steps when the fetched target already matches the local checkout ([#2503](https://github.com/badlogic/pi-mono/issues/2503))
|
||||
|
||||
## [0.61.1] - 2026-03-20
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -1278,6 +1278,23 @@ export class DefaultPackageManager implements PackageManager {
|
||||
return match[1];
|
||||
}
|
||||
|
||||
private async getLocalGitUpdateTarget(installedPath: string): Promise<{ ref: string; head: string }> {
|
||||
try {
|
||||
const head = await this.runCommandCapture("git", ["rev-parse", "@{upstream}"], {
|
||||
cwd: installedPath,
|
||||
timeoutMs: NETWORK_TIMEOUT_MS,
|
||||
});
|
||||
return { ref: "@{upstream}", head };
|
||||
} catch {
|
||||
await this.runCommand("git", ["remote", "set-head", "origin", "-a"], { cwd: installedPath }).catch(() => {});
|
||||
const head = await this.runCommandCapture("git", ["rev-parse", "origin/HEAD"], {
|
||||
cwd: installedPath,
|
||||
timeoutMs: NETWORK_TIMEOUT_MS,
|
||||
});
|
||||
return { ref: "origin/HEAD", head };
|
||||
}
|
||||
}
|
||||
|
||||
private async getGitUpstreamRef(installedPath: string): Promise<string | undefined> {
|
||||
try {
|
||||
const upstream = await this.runCommandCapture("git", ["rev-parse", "--abbrev-ref", "@{upstream}"], {
|
||||
@@ -1463,14 +1480,17 @@ 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 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 });
|
||||
const localHead = await this.runCommandCapture("git", ["rev-parse", "HEAD"], {
|
||||
cwd: targetDir,
|
||||
timeoutMs: NETWORK_TIMEOUT_MS,
|
||||
});
|
||||
const target = await this.getLocalGitUpdateTarget(targetDir);
|
||||
if (localHead.trim() === target.head.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.runCommand("git", ["reset", "--hard", target.ref], { cwd: targetDir });
|
||||
|
||||
// Clean untracked files (extensions should be pristine)
|
||||
await this.runCommand("git", ["clean", "-fdx"], { cwd: targetDir });
|
||||
|
||||
|
||||
@@ -107,6 +107,45 @@ describe("DefaultPackageManager git update", () => {
|
||||
}
|
||||
|
||||
describe("normal updates (no force-push)", () => {
|
||||
it("should skip reset, clean, and install when already up to date", async () => {
|
||||
mkdirSync(remoteDir, { recursive: true });
|
||||
git(["init"], remoteDir);
|
||||
git(["config", "--local", "user.email", "test@test.com"], remoteDir);
|
||||
git(["config", "--local", "user.name", "Test"], remoteDir);
|
||||
writeFileSync(join(remoteDir, "package.json"), JSON.stringify({ name: "test-extension", version: "1.0.0" }));
|
||||
createCommit(remoteDir, "extension.ts", "// v1", "Initial commit");
|
||||
|
||||
mkdirSync(join(agentDir, "git", "github.com", "test"), { recursive: true });
|
||||
git(["clone", remoteDir, installedDir], tempDir);
|
||||
settingsManager.setPackages([gitSource]);
|
||||
|
||||
const executedCommands: string[] = [];
|
||||
const managerWithInternals = packageManager as unknown as {
|
||||
runCommand: (command: string, args: string[], options?: { cwd?: string }) => Promise<void>;
|
||||
};
|
||||
managerWithInternals.runCommand = async (command, args, options) => {
|
||||
executedCommands.push(`${command} ${args.join(" ")}`);
|
||||
if (command === "npm") {
|
||||
return;
|
||||
}
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: options?.cwd,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Command failed: ${command} ${args.join(" ")}\n${result.stderr}`);
|
||||
}
|
||||
};
|
||||
|
||||
await packageManager.update();
|
||||
|
||||
expect(executedCommands).toContain("git fetch --prune origin");
|
||||
expect(executedCommands).not.toContain("git reset --hard @{upstream}");
|
||||
expect(executedCommands).not.toContain("git reset --hard origin/HEAD");
|
||||
expect(executedCommands).not.toContain("git clean -fdx");
|
||||
expect(executedCommands).not.toContain("npm install");
|
||||
});
|
||||
|
||||
it("should update to latest commit when remote has new commits", async () => {
|
||||
setupRemoteAndInstall();
|
||||
expect(getFileContent(installedDir, "extension.ts")).toBe("// v1");
|
||||
@@ -269,6 +308,7 @@ describe("DefaultPackageManager git update", () => {
|
||||
const executedCommands: string[] = [];
|
||||
const managerWithInternals = packageManager as unknown as {
|
||||
runCommand: (command: string, args: string[], options?: { cwd?: string }) => Promise<void>;
|
||||
runCommandCapture: (command: string, args: string[], options?: { cwd?: string }) => Promise<string>;
|
||||
};
|
||||
managerWithInternals.runCommand = async (command, args) => {
|
||||
executedCommands.push(`${command} ${args.join(" ")}`);
|
||||
@@ -276,6 +316,15 @@ describe("DefaultPackageManager git update", () => {
|
||||
writeFileSync(extensionFile, "// fresh");
|
||||
}
|
||||
};
|
||||
managerWithInternals.runCommandCapture = async (_command, args) => {
|
||||
if (args[0] === "rev-parse" && args[1] === "HEAD") {
|
||||
return "local-head";
|
||||
}
|
||||
if (args[0] === "rev-parse" && args[1] === "@{upstream}") {
|
||||
return "remote-head";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
await packageManager.resolveExtensionSources([gitSource], { temporary: true });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user