fix: use mainRepoRoot for agent creation in Paseo-owned worktrees

This commit is contained in:
Mohamed Boudra
2026-01-31 12:04:32 +07:00
parent ea33d1980f
commit c57c86286a
7 changed files with 74 additions and 7 deletions
@@ -117,6 +117,8 @@ describe("checkout git utilities", () => {
expect(status.isGit).toBe(true);
expect(status.repoRoot).toBe(result.worktreePath);
expect(status.isDirty).toBe(true);
expect(status.isPaseoOwnedWorktree).toBe(true);
expect(status.mainRepoRoot).toBe(repoDir);
const diff = await getCheckoutDiff(result.worktreePath, { mode: "uncommitted" }, { paseoHome });
expect(diff.diff).toContain("-hello");
@@ -134,6 +136,29 @@ describe("checkout git utilities", () => {
expect(message).toBe("worktree update");
});
it("returns mainRepoRoot pointing to first non-bare worktree for bare repos", async () => {
const bareRepoDir = join(tempDir, "bare-repo");
execSync(`git clone --bare ${repoDir} ${bareRepoDir}`);
const mainCheckoutDir = join(tempDir, "main-checkout");
execSync(`git -C ${bareRepoDir} worktree add ${mainCheckoutDir} main`);
execSync("git config user.email 'test@test.com'", { cwd: mainCheckoutDir });
execSync("git config user.name 'Test'", { cwd: mainCheckoutDir });
const worktree = await createWorktree({
branchName: "feature",
cwd: mainCheckoutDir,
baseBranch: "main",
worktreeSlug: "feature-worktree",
paseoHome,
});
const status = await getCheckoutStatus(worktree.worktreePath, { paseoHome });
expect(status.isGit).toBe(true);
expect(status.isPaseoOwnedWorktree).toBe(true);
expect(status.mainRepoRoot).toBe(mainCheckoutDir);
});
it("merges the current branch into base from a worktree checkout", async () => {
const worktree = await createWorktree({
branchName: "main",
+36 -1
View File
@@ -1,6 +1,7 @@
import { exec, execFile } from "child_process";
import { promisify } from "util";
import { resolve } from "path";
import { resolve, dirname, basename } from "path";
import { realpathSync } from "fs";
import type { ParsedDiffFile } from "../server/utils/diff-highlighter.js";
import { parseAndHighlightDiff } from "../server/utils/diff-highlighter.js";
import { isPaseoOwnedWorktreeCwd } from "./worktree.js";
@@ -79,6 +80,7 @@ export type CheckoutStatusGitNonPaseo = {
export type CheckoutStatusGitPaseo = {
isGit: true;
repoRoot: string;
mainRepoRoot: string;
currentBranch: string | null;
isDirty: boolean;
baseRef: string;
@@ -156,9 +158,37 @@ async function getWorktreeRoot(cwd: string): Promise<string | null> {
}
}
async function getMainRepoRoot(cwd: string): Promise<string> {
const { stdout: commonDirOut } = await execAsync(
"git rev-parse --path-format=absolute --git-common-dir",
{ cwd, env: READ_ONLY_GIT_ENV }
);
const commonDir = commonDirOut.trim();
const normalized = realpathSync(commonDir);
if (basename(normalized) === ".git") {
return dirname(normalized);
}
const { stdout: worktreeOut } = await execAsync("git worktree list --porcelain", {
cwd,
env: READ_ONLY_GIT_ENV,
});
const worktrees = parseWorktreeList(worktreeOut);
const nonBareNonPaseo = worktrees.filter(
(wt) => !wt.isBare && !wt.path.includes("/.paseo/worktrees/")
);
const childrenOfBareRepo = nonBareNonPaseo.filter((wt) =>
wt.path.startsWith(normalized + "/")
);
const mainChild = childrenOfBareRepo.find((wt) => basename(wt.path) === "main");
return mainChild?.path ?? childrenOfBareRepo[0]?.path ?? nonBareNonPaseo[0]?.path ?? normalized;
}
type GitWorktreeEntry = {
path: string;
branchRef?: string;
isBare?: boolean;
};
function parseWorktreeList(output: string): GitWorktreeEntry[] {
@@ -179,6 +209,9 @@ function parseWorktreeList(output: string): GitWorktreeEntry[] {
if (current && trimmed.startsWith("branch ")) {
current.branchRef = trimmed.slice("branch ".length).trim();
}
if (current && trimmed === "bare") {
current.isBare = true;
}
}
if (current) {
entries.push(current);
@@ -460,9 +493,11 @@ export async function getCheckoutStatus(
hasRemote && currentBranch ? await getAheadOfOrigin(cwd, currentBranch) : null;
if (configured.isPaseoOwnedWorktree) {
const mainRepoRoot = await getMainRepoRoot(cwd);
return {
isGit: true,
repoRoot: worktreeRoot,
mainRepoRoot,
currentBranch,
isDirty,
baseRef: configured.baseRef,