FN-5794: run worktree init for fresh merge workspaces
Run merge worktree initialization automatically when a merge workspace is newly created. - detect when integration worktree creation is fresh and invoke the configured init command - preserve existing behavior for already-initialized worktrees while improving setup reliability - add integration tests covering init command execution paths for merge worktree setup Files changed: .changeset/fn-5794-merge-worktree-init.md | 5 +++ .../__tests__/merger-integration-worktree.test.ts | 16 +++++++++ packages/engine/src/merger.ts | 39 +++++++++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-5794 Fusion-Task-Lineage: f368099f-a179-466e-b994-2a503167112c
This commit is contained in:
5
.changeset/fn-5794-merge-worktree-init.md
Normal file
5
.changeset/fn-5794-merge-worktree-init.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Run the configured `worktreeInitCommand` when the merger has to create a fresh merge worktree during reuse-worktree reacquisition. This bootstraps newly created merge workspaces before merge verification/workflow steps run, while leaving pooled/reused existing worktrees unchanged.
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createMockStore,
|
createMockStore,
|
||||||
@@ -222,6 +223,21 @@ describe("probeIntegrationWorktreeState", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("merger fresh reacquire init wiring", () => {
|
||||||
|
it("wires fresh acquireTaskWorktree calls with init command execution", () => {
|
||||||
|
const mergerSource = readFileSync(new URL("../merger.ts", import.meta.url), "utf-8");
|
||||||
|
const freshAcquireBlock = mergerSource.match(/const acquisition = await acquireTaskWorktree\(\{[\s\S]*?\n\s*\}\);/);
|
||||||
|
expect(freshAcquireBlock?.[0]).toContain("runInitCommand: true");
|
||||||
|
expect(freshAcquireBlock?.[0]).toContain("runConfiguredCommand:");
|
||||||
|
expect(freshAcquireBlock?.[0]).toContain("runConfiguredMergeWorktreeCommand");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps direct-reuse shortcut on existing registrations", () => {
|
||||||
|
const mergerSource = readFileSync(new URL("../merger.ts", import.meta.url), "utf-8");
|
||||||
|
expect(mergerSource).toContain("Skip acquireTaskWorktree's");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("acquireReuseHandoff", () => {
|
describe("acquireReuseHandoff", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|||||||
@@ -7781,7 +7781,9 @@ export async function aiMergeTask(
|
|||||||
logger: mergerLog,
|
logger: mergerLog,
|
||||||
audit,
|
audit,
|
||||||
runContext: engineRunContext,
|
runContext: engineRunContext,
|
||||||
runInitCommand: false,
|
runInitCommand: true,
|
||||||
|
runConfiguredCommand: async (command, cwd, timeoutMs, env) =>
|
||||||
|
runConfiguredMergeWorktreeCommand(command, cwd, timeoutMs, env, audit),
|
||||||
createWorktree: async (branch, path, _taskId, _startPoint, _allowSiblingBranchRename) => {
|
createWorktree: async (branch, path, _taskId, _startPoint, _allowSiblingBranchRename) => {
|
||||||
await execAsync(`git worktree add -f ${quoteArg(path)} ${quoteArg(branch)}`, {
|
await execAsync(`git worktree add -f ${quoteArg(path)} ${quoteArg(branch)}`, {
|
||||||
cwd: projectRootDir,
|
cwd: projectRootDir,
|
||||||
@@ -11929,6 +11931,41 @@ function getPostMergeScriptSandboxBackend(auditor?: RunAuditor): SandboxBackend
|
|||||||
return resolveSandboxBackend({ auditor });
|
return resolveSandboxBackend({ auditor });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runConfiguredMergeWorktreeCommand(
|
||||||
|
command: string,
|
||||||
|
cwd: string,
|
||||||
|
timeoutMs: number,
|
||||||
|
extraEnv?: NodeJS.ProcessEnv,
|
||||||
|
auditor?: RunAuditor,
|
||||||
|
): Promise<{
|
||||||
|
stdout?: string;
|
||||||
|
stderr?: string;
|
||||||
|
exitCode?: number | null;
|
||||||
|
signal?: NodeJS.Signals | null;
|
||||||
|
timedOut?: boolean;
|
||||||
|
bufferExceeded?: boolean;
|
||||||
|
spawnError?: Error;
|
||||||
|
}> {
|
||||||
|
const backend = getPostMergeScriptSandboxBackend(auditor);
|
||||||
|
const result = await backend.run(command, {
|
||||||
|
cwd,
|
||||||
|
encoding: "utf-8",
|
||||||
|
timeoutMs,
|
||||||
|
maxBuffer: 10 * 1024 * 1024,
|
||||||
|
...(extraEnv !== undefined && { env: extraEnv }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
stdout: result.stdout,
|
||||||
|
stderr: result.stderr,
|
||||||
|
exitCode: result.exitCode,
|
||||||
|
signal: result.signal,
|
||||||
|
timedOut: result.timedOut,
|
||||||
|
bufferExceeded: result.bufferExceeded,
|
||||||
|
spawnError: result.spawnError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** Execute a script-mode post-merge workflow step in the provided execution directory. */
|
/** Execute a script-mode post-merge workflow step in the provided execution directory. */
|
||||||
async function executePostMergeScriptStep(
|
async function executePostMergeScriptStep(
|
||||||
store: TaskStore,
|
store: TaskStore,
|
||||||
|
|||||||
Reference in New Issue
Block a user