fix: validate auto-worktree is a real git worktree before use (#695)
getAutoWorktreePath() only checked existsSync() on the worktree directory, treating any directory under .gsd/worktrees/<MID>/ as a valid auto-worktree. A stray (non-git) directory would be accepted, causing auto-mode to derive state from an empty/invalid path and conclude no milestones exist. Add git worktree validation to both getAutoWorktreePath() and enterAutoWorktree(): check that the directory contains a .git file (not directory) with a 'gitdir:' pointer, which is the hallmark of a real git worktree checkout. Return null / throw if validation fails. This ensures stray directories are ignored and auto-mode falls through to normal worktree creation or root-state derivation. Closes #695
This commit is contained in:
parent
a90aa0c8d6
commit
e9a2928ce7
1 changed files with 35 additions and 2 deletions
|
|
@ -264,11 +264,29 @@ export function isInAutoWorktree(basePath: string): boolean {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the filesystem path for an auto-worktree, or null if it doesn't exist.
|
||||
* Get the filesystem path for an auto-worktree, or null if it doesn't exist
|
||||
* or is not a valid git worktree.
|
||||
*
|
||||
* Validates that the path is a real git worktree (has a .git file with a
|
||||
* gitdir: pointer) rather than just a stray directory. This prevents
|
||||
* mis-detection of leftover directories as active worktrees (#695).
|
||||
*/
|
||||
export function getAutoWorktreePath(basePath: string, milestoneId: string): string | null {
|
||||
const p = worktreePath(basePath, milestoneId);
|
||||
return existsSync(p) ? p : null;
|
||||
if (!existsSync(p)) return null;
|
||||
|
||||
// Validate this is a real git worktree, not a stray directory.
|
||||
// A git worktree has a .git *file* (not directory) containing "gitdir: <path>".
|
||||
const gitPath = join(p, ".git");
|
||||
if (!existsSync(gitPath)) return null;
|
||||
try {
|
||||
const content = readFileSync(gitPath, "utf8").trim();
|
||||
if (!content.startsWith("gitdir: ")) return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -283,6 +301,21 @@ export function enterAutoWorktree(basePath: string, milestoneId: string): string
|
|||
throw new Error(`Auto-worktree for ${milestoneId} does not exist at ${p}`);
|
||||
}
|
||||
|
||||
// Validate this is a real git worktree, not a stray directory (#695)
|
||||
const gitPath = join(p, ".git");
|
||||
if (!existsSync(gitPath)) {
|
||||
throw new Error(`Auto-worktree path ${p} exists but is not a git worktree (no .git)`);
|
||||
}
|
||||
try {
|
||||
const content = readFileSync(gitPath, "utf8").trim();
|
||||
if (!content.startsWith("gitdir: ")) {
|
||||
throw new Error(`Auto-worktree path ${p} has a .git but it is not a worktree gitdir pointer`);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes("worktree")) throw err;
|
||||
throw new Error(`Auto-worktree path ${p} exists but .git is unreadable`);
|
||||
}
|
||||
|
||||
const previousCwd = process.cwd();
|
||||
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue