7ba2227c3c
Worktree setup and autoresearch Git prep silently misbehaved in pure Jujutsu workspaces (`.jj/repo/` present, no colocated `.git/`): - `task/worktree.ts#getRepoRoot` threw a generic "Git repository not found for isolated task execution.", giving a jj user no hint about what was wrong. - `autoresearch/git.ts#ensureAutoresearchBranch` returned a soft "Not in a git repository" warning, letting `/autoresearch` proceed with no branch isolation, baseline reset, or auto-commits. Both paths now detect a pure jj workspace via a new `jj.isPureJjRepo` helper and surface an actionable Jujutsu-specific error pointing at `jj git init --colocate`. Colocated jj-git workspaces (both `.jj/` and `.git/` at the same root) and plain Git checkouts behave exactly as before. Fixes #1935
96 lines
3.6 KiB
TypeScript
96 lines
3.6 KiB
TypeScript
import { afterEach, describe, expect, it } from "bun:test";
|
|
import * as fs from "node:fs/promises";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { ensureAutoresearchBranch } from "../src/autoresearch/git";
|
|
import type { ExtensionAPI } from "../src/extensibility/extensions";
|
|
import * as jj from "../src/utils/jj";
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
async function mkTempDir(prefix: string): Promise<string> {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
async function runGit(cwd: string, args: string[]): Promise<void> {
|
|
const env = { ...process.env, HOME: cwd, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_SYSTEM: "/dev/null" };
|
|
const proc = Bun.spawn(["git", "-C", cwd, ...args], { env, stdout: "ignore", stderr: "pipe" });
|
|
const code = await proc.exited;
|
|
if (code !== 0) {
|
|
const stderr = await new Response(proc.stderr).text();
|
|
throw new Error(`git ${args.join(" ")} failed (${code}): ${stderr}`);
|
|
}
|
|
}
|
|
|
|
async function initGitWithCommit(dir: string): Promise<void> {
|
|
await runGit(dir, ["init", "-q", "-b", "main"]);
|
|
await runGit(dir, ["config", "user.email", "test@example.com"]);
|
|
await runGit(dir, ["config", "user.name", "Test"]);
|
|
await fs.writeFile(path.join(dir, "README"), "seed\n");
|
|
await runGit(dir, ["add", "."]);
|
|
await runGit(dir, ["commit", "-q", "-m", "init"]);
|
|
}
|
|
|
|
// `ensureAutoresearchBranch` never invokes the `api` it receives — its two
|
|
// internal helpers (`readGitWorkDirPrefix`, `branchExists`) immediately
|
|
// `void api`. The stub keeps the signature honest without spinning up the
|
|
// real extension runtime.
|
|
const stubApi = {} as unknown as ExtensionAPI;
|
|
|
|
afterEach(async () => {
|
|
jj.repo.clearRootCache();
|
|
await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true })));
|
|
});
|
|
|
|
describe("ensureAutoresearchBranch jj guardrails", () => {
|
|
it("rejects pure jj workspaces before touching git state", async () => {
|
|
const dir = await mkTempDir("omp-ar-purejj-");
|
|
await fs.mkdir(path.join(dir, ".jj", "repo", "store"), { recursive: true });
|
|
|
|
const result = await ensureAutoresearchBranch(stubApi, dir, "demo");
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("unreachable");
|
|
expect(result.error).toMatch(/pure Jujutsu/);
|
|
expect(result.error).toMatch(/jj git init --colocate/);
|
|
});
|
|
|
|
it("creates an autoresearch branch in a colocated jj-git workspace", async () => {
|
|
const dir = await mkTempDir("omp-ar-colocated-");
|
|
await initGitWithCommit(dir);
|
|
await fs.mkdir(path.join(dir, ".jj", "repo", "store"), { recursive: true });
|
|
|
|
const result = await ensureAutoresearchBranch(stubApi, dir, "demo goal");
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (!result.ok) throw new Error("unreachable");
|
|
expect(result.created).toBe(true);
|
|
expect(result.branchName).toMatch(/^autoresearch\/demo-goal-\d{8}$/);
|
|
});
|
|
|
|
it("creates an autoresearch branch in a plain git repo (unchanged behavior)", async () => {
|
|
const dir = await mkTempDir("omp-ar-plaingit-");
|
|
await initGitWithCommit(dir);
|
|
|
|
const result = await ensureAutoresearchBranch(stubApi, dir, "demo");
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (!result.ok) throw new Error("unreachable");
|
|
expect(result.created).toBe(true);
|
|
expect(result.branchName).toMatch(/^autoresearch\/demo-\d{8}$/);
|
|
});
|
|
|
|
it("returns the soft no-git warning for directories backed by neither tool", async () => {
|
|
const dir = await mkTempDir("omp-ar-empty-");
|
|
|
|
const result = await ensureAutoresearchBranch(stubApi, dir, "demo");
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (!result.ok) throw new Error("unreachable");
|
|
expect(result.branchName).toBeNull();
|
|
expect(result.warning).toMatch(/Not in a git repository/);
|
|
});
|
|
});
|