Merge pull request #3205: feat(eval): isolated/apply/merge options for agent() helper
Resolves conflict in test/task/worktree.test.ts by keeping both the getRepoRoot (main) and applyNestedPatches (PR) describe blocks. Extends the PR's Python/JS work to the remaining workflow runtimes: - eval/rb/prelude.rb, eval/jl/prelude.jl: agent() now accepts and forwards isolated/apply/merge (as booleans) plus returnHandle, and the return_handle node carries isolated/patch_path/branch_name/ nested_patches/changes_applied/isolation_summary. Post-merge fixups: - task/index.ts: drop dead commitStyle var (the dedup refactor reads task.isolation.commits inside makeIsolationCommitMessage). - CHANGELOG: move the misplaced Added entry under [Unreleased], correct the stale "defaults track task.isolation.mode" wording to the final strict opt-in behavior, and note all four runtimes. Fixes #3196
This commit is contained in:
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import {
|
||||
applyNestedPatches,
|
||||
captureBaseline,
|
||||
captureDeltaPatch,
|
||||
ensureIsolation,
|
||||
@@ -279,3 +280,88 @@ describe("getRepoRoot", () => {
|
||||
expect(await getRepoRoot(inner)).toBe(inner);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyNestedPatches", () => {
|
||||
let parentRepo: string;
|
||||
let nestedRel: string;
|
||||
let nestedDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
parentRepo = await fs.mkdtemp(path.join(os.tmpdir(), "omp-nested-apply-"));
|
||||
await runGit(parentRepo, ["init", "-q", "-b", "main"]);
|
||||
await runGit(parentRepo, ["config", "user.email", "test@example.com"]);
|
||||
await runGit(parentRepo, ["config", "user.name", "Test User"]);
|
||||
await fs.writeFile(path.join(parentRepo, ".gitignore"), "sub/\n");
|
||||
await runGit(parentRepo, ["add", "."]);
|
||||
await runGit(parentRepo, ["commit", "-q", "-m", "parent-init"]);
|
||||
|
||||
nestedRel = "sub";
|
||||
nestedDir = path.join(parentRepo, nestedRel);
|
||||
await fs.mkdir(nestedDir, { recursive: true });
|
||||
await runGit(nestedDir, ["init", "-q", "-b", "main"]);
|
||||
await runGit(nestedDir, ["config", "user.email", "test@example.com"]);
|
||||
await runGit(nestedDir, ["config", "user.name", "Test User"]);
|
||||
await fs.writeFile(path.join(nestedDir, "file.txt"), "v1\n");
|
||||
await runGit(nestedDir, ["add", "."]);
|
||||
await runGit(nestedDir, ["commit", "-q", "-m", "nested-init"]);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.rm(parentRepo, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("does not fold pre-existing dirty nested-repo state into the agent commit", async () => {
|
||||
// User has unrelated work-in-progress in the nested repo before the agent runs.
|
||||
await fs.writeFile(path.join(nestedDir, "other.txt"), "user wip\n");
|
||||
|
||||
const patch =
|
||||
"diff --git a/file.txt b/file.txt\n" +
|
||||
"--- a/file.txt\n" +
|
||||
"+++ b/file.txt\n" +
|
||||
"@@ -1 +1 @@\n" +
|
||||
"-v1\n" +
|
||||
"+v2\n";
|
||||
await applyNestedPatches(parentRepo, [{ relativePath: nestedRel, patch }]);
|
||||
|
||||
const [committedFiles, headContent, otherContent, statusPorcelain] = await Promise.all([
|
||||
runGit(nestedDir, ["log", "-1", "--name-only", "--pretty=format:"]),
|
||||
fs.readFile(path.join(nestedDir, "file.txt"), "utf8"),
|
||||
fs.readFile(path.join(nestedDir, "other.txt"), "utf8"),
|
||||
runGit(nestedDir, ["status", "--porcelain=v1"]),
|
||||
]);
|
||||
expect(committedFiles.trim()).toBe("file.txt");
|
||||
expect(headContent).toBe("v2\n");
|
||||
expect(otherContent).toBe("user wip\n");
|
||||
expect(statusPorcelain).toBe("?? other.txt");
|
||||
});
|
||||
|
||||
it("restores pre-existing staged WIP to the index, not just the working tree", async () => {
|
||||
// Pre-existing tracked file with a staged edit; the patch should leave
|
||||
// this entirely alone, and the stash pop must re-stage it (--index).
|
||||
await fs.writeFile(path.join(nestedDir, "other.txt"), "tracked v1\n");
|
||||
await runGit(nestedDir, ["add", "other.txt"]);
|
||||
await runGit(nestedDir, ["commit", "-q", "-m", "add-other"]);
|
||||
await fs.writeFile(path.join(nestedDir, "other.txt"), "staged wip\n");
|
||||
await runGit(nestedDir, ["add", "other.txt"]);
|
||||
|
||||
const patch =
|
||||
"diff --git a/file.txt b/file.txt\n" +
|
||||
"--- a/file.txt\n" +
|
||||
"+++ b/file.txt\n" +
|
||||
"@@ -1 +1 @@\n" +
|
||||
"-v1\n" +
|
||||
"+v2\n";
|
||||
await applyNestedPatches(parentRepo, [{ relativePath: nestedRel, patch }]);
|
||||
|
||||
const [committedFiles, statusPorcelain, cachedDiff] = await Promise.all([
|
||||
runGit(nestedDir, ["log", "-1", "--name-only", "--pretty=format:"]),
|
||||
runGit(nestedDir, ["status", "--porcelain=v1"]),
|
||||
runGit(nestedDir, ["diff", "--cached", "--", "other.txt"]),
|
||||
]);
|
||||
expect(committedFiles.trim()).toBe("file.txt");
|
||||
// Leading "M " (with trailing space) marks an index-only modification —
|
||||
// "M" in the first slot, " " in the second. " M" would mean unstaged.
|
||||
expect(statusPorcelain).toBe("M other.txt");
|
||||
expect(cachedDiff).toContain("+staged wip");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user