1/** Worktrees use isolated .t3 state; ambient T3CODE_HOME could otherwise select the live userdata database. */
2
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Option from "effect/Option";
6import * as Path from "effect/Path";
7
8// Match worktrees/<name>; bare repos and GIT_COMMON_DIR need not use a directory named .git.
9const pointsAtLinkedWorktree = (gitFileContents: string, path: Path.Path): boolean => {
10 const gitdir = gitFileContents
11 .split(/\r?\n/)
12 .map((line) => line.trim())
13 .find((line) => line.startsWith("gitdir:"))
14 ?.slice("gitdir:".length)
15 .trim();
16 if (gitdir === undefined || gitdir.length === 0) {
17 return false;
18 }
19 const segments = path
20 .normalize(gitdir.replaceAll("\\", "/"))
21 .split(/[/\\]/)
22 .filter((segment) => segment.length > 0);
23 return segments.length >= 3 && segments.at(-2) === "worktrees";
24};
25
26export const resolveGitWorktreePath = Effect.fn("devHome.resolveGitWorktreePath")(function* (
27 cwd: string,
28): Effect.fn.Return<string | undefined, never, FileSystem.FileSystem | Path.Path> {
29 const fileSystem = yield* FileSystem.FileSystem;
30 const path = yield* Path.Path;
31
32 let directory = path.resolve(cwd);
33 for (;;) {
34 const gitPath = path.join(directory, ".git");
35 const info = yield* fileSystem.stat(gitPath).pipe(Effect.option);
36 if (Option.isSome(info)) {
37 if (info.value.type !== "File") {
38 return undefined;
39 }
40 const contents = yield* fileSystem
41 .readFileString(gitPath)
42 .pipe(Effect.orElseSucceed(() => ""));
43 return pointsAtLinkedWorktree(contents, path) ? directory : undefined;
44 }
45 const parent = path.dirname(directory);
46 if (parent === directory) {
47 return undefined;
48 }
49 directory = parent;
50 }
51});
52
53/**
54 * Deliberately does not require the directory to exist yet: falling back
55 * because it is missing would send callers at the shared home.
56 */
57export const resolveWorktreeT3Home = Effect.fn("devHome.resolveWorktreeT3Home")(function* (
58 cwd: string,
59): Effect.fn.Return<string | undefined, never, FileSystem.FileSystem | Path.Path> {
60 const worktreePath = yield* resolveGitWorktreePath(cwd);
61 if (worktreePath === undefined) {
62 return undefined;
63 }
64 const path = yield* Path.Path;
65 return path.join(worktreePath, ".t3");
66});