* fix(git): enable core.longpaths for worktree population Worktrees live under a deep OpenCode data-dir path, so Windows checkouts of deeply nested repos failed bootstrap with "Filename too long". Enable Git core.longpaths before git reset --hard (web + VS Code) and surface clearer path-length guidance when the filesystem still rejects a path. Fixes #2746 Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * chore(vscode): keep ensureWorktreeLongpaths private Avoid an unused export in the VS Code git service; the helper stays local to populateWorktreeWithLockRecovery. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
Serhii Dziupin
Cursor Agent
parent
87dbc59bf1
commit
10606d79d3
@@ -25,6 +25,7 @@ Keep `bridge.ts` as a thin orchestration layer that delegates message handling t
|
||||
- Owns VS Code Git and worktree operations.
|
||||
- Fast worktree creation reports bootstrap phases explicitly: `directory-created`, then `git-ready` after Git population/upstream work, and `setup-ready` after setup commands. Existing worktrees without tracked bootstrap state fall back to `ready`/`setup-ready`; shared webview consumers also accept legacy responses without `phase`.
|
||||
- Worktree removal waits for an active create/bootstrap task for the same directory so background Git and setup work cannot race deletion or restore stale bootstrap state.
|
||||
- Worktree population enables Git `core.longpaths` (local repo config plus `-c core.longpaths=true` on `git reset --hard`) so deeply nested checkouts under the managed data-dir worktree root do not fail on Windows MAX_PATH with "Filename too long".
|
||||
|
||||
- `bridge-fs-runtime.ts`
|
||||
- Bridge handlers for filesystem-related message routes.
|
||||
|
||||
@@ -1117,34 +1117,71 @@ const getFileIdentity = async (filePath: string): Promise<string | null> => {
|
||||
}
|
||||
};
|
||||
|
||||
// OpenChamber places managed worktrees under a deep data-dir path
|
||||
// (`<XDG_DATA_HOME>/opencode/worktree/<40-char project id>/<name>/`). On
|
||||
// Windows that prefix plus a deeply nested repo file routinely exceeds
|
||||
// MAX_PATH (260). Git can check those paths out when core.longpaths is
|
||||
// enabled; without it, `git reset --hard` during bootstrap fails with
|
||||
// "Filename too long" and leaves a half-populated worktree (issue #2746).
|
||||
const WORKTREE_POPULATE_RESET_ARGS = ['-c', 'core.longpaths=true', 'reset', '--hard'] as const;
|
||||
|
||||
const isFilenameTooLongError = (message: string | null | undefined): boolean =>
|
||||
/file ?name too long/i.test(String(message || ''));
|
||||
|
||||
const formatWorktreePopulateError = (message: string | null | undefined): string => {
|
||||
const text = String(message || '').trim() || 'Failed to populate worktree';
|
||||
if (!isFilenameTooLongError(text)) {
|
||||
return text;
|
||||
}
|
||||
return [
|
||||
text,
|
||||
'The worktree checkout path exceeds this system\'s path-length limit.',
|
||||
'OpenChamber enables Git `core.longpaths` for worktree population; if this still fails on Windows, enable OS long paths (LongPathsEnabled) or open the repository from a shorter absolute path.',
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
const ensureWorktreeLongpaths = async (directory: string): Promise<void> => {
|
||||
const current = await runGitCommand(directory, ['config', '--get', 'core.longpaths']);
|
||||
if (String(current.stdout || '').trim().toLowerCase() === 'true') {
|
||||
return;
|
||||
}
|
||||
// Local config is shared across linked worktrees via the common git dir, so
|
||||
// subsequent OpenChamber and CLI git operations in this repo also get long
|
||||
// path support. Failures here are non-fatal: populate still passes
|
||||
// `-c core.longpaths=true` on reset.
|
||||
await runGitCommand(directory, ['config', 'core.longpaths', 'true']);
|
||||
};
|
||||
|
||||
const populateWorktreeWithLockRecovery = async (directory: string): Promise<void> => {
|
||||
let result = await runGitCommand(directory, ['reset', '--hard']);
|
||||
await ensureWorktreeLongpaths(directory);
|
||||
|
||||
let result = await runGitCommand(directory, [...WORKTREE_POPULATE_RESET_ARGS]);
|
||||
if (result.success) {
|
||||
return;
|
||||
}
|
||||
if (!isIndexLockError(result)) {
|
||||
throw new Error(result.message || 'Failed to populate worktree');
|
||||
throw new Error(formatWorktreePopulateError(result.message));
|
||||
}
|
||||
|
||||
await wait(WORKTREE_INDEX_LOCK_RETRY_DELAY_MS);
|
||||
result = await runGitCommand(directory, ['reset', '--hard']);
|
||||
result = await runGitCommand(directory, [...WORKTREE_POPULATE_RESET_ARGS]);
|
||||
if (result.success) {
|
||||
return;
|
||||
}
|
||||
if (!isIndexLockError(result)) {
|
||||
throw new Error(result.message || 'Failed to populate worktree');
|
||||
throw new Error(formatWorktreePopulateError(result.message));
|
||||
}
|
||||
|
||||
const lockPath = await getWorktreeIndexLockPath(directory);
|
||||
const identity = lockPath ? await getFileIdentity(lockPath) : null;
|
||||
await wait(WORKTREE_INDEX_LOCK_STALE_DELAY_MS);
|
||||
|
||||
result = await runGitCommand(directory, ['reset', '--hard']);
|
||||
result = await runGitCommand(directory, [...WORKTREE_POPULATE_RESET_ARGS]);
|
||||
if (result.success) {
|
||||
return;
|
||||
}
|
||||
if (!isIndexLockError(result) || !lockPath || !identity || await getFileIdentity(lockPath) !== identity) {
|
||||
throw new Error(result.message || 'Failed to populate worktree');
|
||||
throw new Error(formatWorktreePopulateError(result.message));
|
||||
}
|
||||
|
||||
await fs.promises.unlink(lockPath).catch((error) => {
|
||||
@@ -1152,7 +1189,10 @@ const populateWorktreeWithLockRecovery = async (directory: string): Promise<void
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
await runGitCommandOrThrow(directory, ['reset', '--hard'], 'Failed to populate worktree');
|
||||
const finalResult = await runGitCommand(directory, [...WORKTREE_POPULATE_RESET_ARGS]);
|
||||
if (!finalResult.success) {
|
||||
throw new Error(formatWorktreePopulateError(finalResult.message || 'Failed to populate worktree'));
|
||||
}
|
||||
};
|
||||
|
||||
// Worktrees are created with `git worktree add --no-checkout` and populated
|
||||
|
||||
Reference in New Issue
Block a user