-
Notifications
You must be signed in to change notification settings - Fork 13
Add setup-worktree support with security checks and improved type safety #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add setup-worktree support with security checks and improved type safety #22
Conversation
- Add getRepoRoot utility function to determine git repository root - Support both .cursor/worktrees.json (Cursor format) and worktrees.json (generic format) - Execute setup commands in new worktree directory with ROOT_WORKTREE_PATH env var - Add comprehensive documentation with examples - Commands run after worktree creation but before dependency installation
- Add getRepoRoot utility function to determine git repository root - Support both .cursor/worktrees.json (Cursor format) and worktrees.json (generic format) - Execute setup commands in new worktree directory with ROOT_WORKTREE_PATH env var - Add security allowlist for safe commands only (npm/yarn/pnpm install, git submodule) - Add comprehensive documentation with examples - Remove build files from version control to keep only source files - Commands run after worktree creation but before dependency installation
- Build files are needed for distribution and should remain tracked - This prevents the pull request from showing unnecessary deletions
- Update WorktreeSetupData type to handle both object and array formats - Remove unsafe 'as any' type assertions - Add proper type guards for safer data access - Maintain backward compatibility with both Cursor and generic formats
WalkthroughThe change removes the legacy compiled CLI and command handlers under build/, introduces a new setup worktree command in src/, adds a Git utility to resolve repo root, wires the new command into the TypeScript CLI, and updates README documentation detailing setup behavior, configuration formats, security checks, and execution rules. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant CLI as wt (setup)
participant Git as Git
participant FS as Filesystem
participant Cfg as Config (.cursor/worktrees.json / worktrees.json)
participant Sh as Shell (setup cmds)
participant PM as Installer (npm/yarn/pnpm)
participant Ed as Editor
U->>CLI: wt setup [branch] [-p path] [-c] [-i pm] [-e editor]
CLI->>Git: rev-parse --is-inside-work-tree
Git-->>CLI: ok / error
alt not in repo
CLI-->>U: exit non-zero
end
CLI->>Git: status --porcelain (main worktree)
CLI->>Git: worktree list --porcelain
CLI->>FS: resolve/ensure target path
alt worktree exists
CLI-->>U: reuse existing worktree
else create new
CLI->>Git: branch/checkout as needed
CLI->>Git: worktree add <path> <branch>
end
CLI->>Cfg: read .cursor/worktrees.json
alt not found
CLI->>Cfg: read worktrees.json
end
CLI->>Sh: run setup commands (filtered by blocklist/allow)
Sh-->>CLI: per-command status (continue on error)
opt install requested (-i)
CLI->>PM: install at target path
PM-->>CLI: done / error (non-fatal)
end
opt editor requested (-e)
CLI->>Ed: open <path>
Ed-->>CLI: launched / error (non-fatal)
end
CLI-->>U: completed (path, branch)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (10)
💤 Files with no reviewable changes (10)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/utils/git.ts (1)
44-67: Make bare-repo detection portable and simpler.Current logic assumes
/.gitsuffix and can break on Windows paths. Usegit rev-parse --is-bare-repositoryinstead.-export async function isMainRepoBare(cwd: string = '.'): Promise<boolean> { - try { - // Find the root of the git repository - const { stdout: gitDir } = await execa('git', ['-C', cwd, 'rev-parse', '--git-dir']); - const mainRepoDir = gitDir.endsWith('/.git') ? gitDir.slice(0, -5) : gitDir; // Handle bare repo paths vs normal .git - - // Check the core.bare setting specifically for that repository path - const { stdout: bareConfig } = await execa('git', ['config', '--get', '--bool', 'core.bare'], { - cwd: mainRepoDir, // Check config in the main repo dir, not the potentially detached worktree CWD - }); - - // stdout will be 'true' or 'false' as a string - return bareConfig.trim() === 'true'; - } catch (error: any) { - // If the command fails (e.g., not a git repo, or config not set), - // assume it's not bare, but log a warning. - // A non-existent core.bare config defaults to false. - if (error.exitCode === 1 && error.stdout === '' && error.stderr === '') { - // This specific exit code/output means the config key doesn't exist, which is fine (defaults to false). - return false; - } - console.warn(chalk.yellow(`Could not reliably determine if the main repository is bare. Proceeding cautiously. Error:`), error.stderr || error.message); - return false; // Default to non-bare to avoid blocking unnecessarily, but warn the user. - } -} +export async function isMainRepoBare(cwd: string = '.'): Promise<boolean> { + try { + const { stdout } = await execa('git', ['-C', cwd, 'rev-parse', '--is-bare-repository']); + return stdout.trim() === 'true'; + } catch (error: any) { + console.warn( + chalk.yellow('Could not reliably determine if the main repository is bare. Proceeding cautiously. Error:'), + error.stderr || error.message + ); + return false; + } +}build/commands/pr.js (1)
41-51: Check for bare repo before cleanliness check.On bare repos,
isWorktreeCleanyields a misleading "not clean" error. Guard earlier and exit.Example insertion right after Line 40:
if (await isMainRepoBare()) { console.error(chalk.red("❌ Error: The main repository is configured as 'bare' (core.bare=true).")); console.error(chalk.red(" This prevents normal Git operations. Please fix the configuration:")); console.error(chalk.cyan(" git config core.bare false")); process.exit(1); }
🧹 Nitpick comments (11)
README.md (1)
111-116: Clarify allowlist behavior in bullets.Add a note that only allowlisted commands run by default.
- Commands are executed in the new worktree directory. - The $ROOT_WORKTREE_PATH environment variable is available, pointing to the main repository root. - Commands run with shell execution, so complex commands and piping are supported. - If a command fails, the error is logged, but setup continues with the next command. - The setup runs after worktree creation but before dependency installation (if `--install` is used). + - Only allowlisted commands are executed. Default allowlist: `npm install`, `yarn install`, `pnpm install`, `git submodule update --init --recursive`. Others are skipped with a warning.build/commands/merge.js (1)
58-63: Fail fast: run bare-repo guard before merge/remove steps.Run the guard immediately after repo validation to avoid partial operations and clearer errors.
Example insertion near Line 8 (after rev-parse):
if (await isMainRepoBare()) { console.error(chalk.red("❌ Error: The main repository is configured as 'bare' (core.bare=true).")); console.error(chalk.red(" This prevents normal Git operations. Please fix the configuration:")); console.error(chalk.cyan(" git config core.bare false")); process.exit(1); }build/commands/remove.js (1)
45-54: Early bare check and add--sentinel to git args.
- Guard earlier for faster failure and clearer UX.
- Use
--to disambiguate path arguments.@@ - console.log(chalk.blue(`Removing worktree: ${targetPath}`)); - // >>> ADD SAFETY CHECK HERE <<< - if (await isMainRepoBare()) { + console.log(chalk.blue(`Removing worktree: ${targetPath}`)); + // Fail fast if main repo is bare + if (await isMainRepoBare()) { console.error(chalk.red("❌ Error: The main repository is configured as 'bare' (core.bare=true).")); console.error(chalk.red(" This prevents normal Git operations. Please fix the configuration:")); console.error(chalk.cyan(" git config core.bare false")); process.exit(1); } @@ - await execa("git", ["worktree", "remove", ...(options.force ? ["--force"] : []), targetPath]); + await execa("git", ["worktree", "remove", ...(options.force ? ["--force"] : []), "--", targetPath]);build/commands/pr.js (2)
165-167: Harden git args with--separator.- await execa("git", ["worktree", "add", resolvedPath, prBranchName]); + await execa("git", ["worktree", "add", "--", resolvedPath, prBranchName]);
197-198: Unify editor launch behavior across commands.This command uses
{ stdio: "ignore", detached: true }while others use{ stdio: "inherit" }. Pick one behavior for consistency (detach vs block). I can update all handlers to match. Based on learnings.build/commands/open.js (2)
8-14: Add bare-repo guard for consistency and clearer errors.Insert the standard
isMainRepoBare()guard after repo validation to align with other commands and avoid confusing errors on bare repos.// After rev-parse validation if (await isMainRepoBare()) { console.error(chalk.red("❌ Error: The main repository is configured as 'bare' (core.bare=true).")); console.error(chalk.red(" This prevents normal Git operations. Please fix the configuration:")); console.error(chalk.cyan(" git config core.bare false")); process.exit(1); }
70-75: Align editor spawn behavior across commands.Decide whether handlers should detach or block and apply uniformly (this one blocks with inherit). I can submit a follow-up to normalize across new/pr/open. Based on learnings.
src/commands/new.ts (2)
98-178: Allowlist execution: avoid shell, map to file/args pairs.You can keep the allowlist and improve safety by avoiding
{ shell: true }. Execute exact allowed commands via file+args.- if (commands.length > 0) { - // Define an allowlist of safe commands - const allowedCommands = [ - "npm install", - "yarn install", - "pnpm install", - "git submodule update --init --recursive" - // Add more allowed commands as needed - ]; - const env = { ...process.env, ROOT_WORKTREE_PATH: repoRoot }; - for (const command of commands) { - // Only execute if the command is in the allowlist - if (allowedCommands.includes(command)) { - console.log(chalk.gray(`Executing: ${command}`)); - try { - await execa(command, { shell: true, cwd: resolvedPath, env, stdio: "inherit" }); - } catch (cmdError: unknown) { + if (commands.length > 0) { + // Define an allowlist of safe commands mapped to file/args + const allowedMap: Record<string, { file: string; args: string[] }> = { + "npm install": { file: "npm", args: ["install"] }, + "yarn install": { file: "yarn", args: ["install"] }, + "pnpm install": { file: "pnpm", args: ["install"] }, + "git submodule update --init --recursive": { + file: "git", + args: ["submodule", "update", "--init", "--recursive"], + }, + }; + const env = { ...process.env, ROOT_WORKTREE_PATH: repoRoot }; + for (const command of commands) { + const entry = allowedMap[command]; + if (entry) { + console.log(chalk.gray(`Executing: ${command}`)); + try { + await execa(entry.file, entry.args, { cwd: resolvedPath, env, stdio: "inherit" }); + } catch (cmdError: unknown) { if (cmdError instanceof Error) { console.error(chalk.red(`Setup command failed: ${command}`), cmdError.message); } else { console.error(chalk.red(`Setup command failed: ${command}`), cmdError); } // Continue with other commands } } else { console.warn(chalk.yellow(`Skipped unsafe or unapproved setup command: "${command}"`)); } } console.log(chalk.green("Setup commands completed."));
82-96: Harden git args with--separator.- await execa("git", ["worktree", "add", "-b", branchName, resolvedPath]); + await execa("git", ["worktree", "add", "-b", branchName, "--", resolvedPath]); @@ - await execa("git", ["worktree", "add", resolvedPath, branchName]); + await execa("git", ["worktree", "add", "--", resolvedPath, branchName]);build/commands/new.js (1)
126-132: Consider expanding the allowlist to include common setup commands.The current allowlist includes only 4 commands:
npm installyarn installpnpm installgit submodule update --init --recursiveConsider adding other common, safe setup commands such as:
bun install(for Bun package manager)npm ci(for CI environments)pnpm ci(if supported)git lfs pull(for Git LFS repositories)This would make the feature more flexible while maintaining security through the allowlist approach.
build/commands/extract.js (1)
60-84: Extract the path derivation logic into a shared utility function.The path derivation logic (lines 66-72) is duplicated from
build/commands/new.js(lines 29-36). This is a clear case of code duplication that could be refactored into a shared utility function.Consider creating a shared utility function in
build/utils/path.jsor similar:/** * Derives a worktree folder path based on branch name * @param {string} branchName - The branch name to derive path from * @param {string} [customPath] - Optional custom path override * @returns {string} The resolved absolute path for the worktree */ export function deriveWorktreePath(branchName, customPath) { if (customPath) { return resolve(customPath); } const shortBranchName = branchName.split('/').filter(part => part.length > 0).pop() || branchName; const currentDir = process.cwd(); const parentDir = dirname(currentDir); const currentDirName = basename(currentDir); const folderName = join(parentDir, `${currentDirName}-${shortBranchName}`); return resolve(folderName); }Then update both
new.jsandextract.jsto use this shared function.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (11)
README.md(1 hunks)build/commands/extract.js(1 hunks)build/commands/merge.js(2 hunks)build/commands/new.js(3 hunks)build/commands/open.js(1 hunks)build/commands/pr.js(2 hunks)build/commands/remove.js(2 hunks)build/index.js(2 hunks)build/utils/git.js(1 hunks)src/commands/new.ts(2 hunks)src/utils/git.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/commands/new.ts
📄 CodeRabbit inference engine (.cursor/rules/project.mdc)
Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.
Files:
src/commands/new.ts
src/commands/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project.mdc)
src/commands/*.ts: Leverage Execa to execute Git commands and other external processes.
Provide clear, colored console feedback for success and error messages in CLI commands.
Files:
src/commands/new.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: juristr
PR: johnlindquist/worktree-cli#20
File: src/commands/extract.ts:124-127
Timestamp: 2025-08-04T14:22:29.156Z
Learning: The worktree-cli project prioritizes consistency across commands. When implementing new commands like `extract`, developers follow existing patterns from similar commands like `new` to maintain API and implementation consistency.
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/new.ts : Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/new.ts : Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.
Applied to files:
README.mdbuild/commands/extract.jssrc/commands/new.tsbuild/commands/open.jsbuild/index.jsbuild/commands/new.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/list.ts : Provide functionality to list existing Git worktrees in src/commands/list.ts.
Applied to files:
README.mdsrc/commands/new.tsbuild/index.js
📚 Learning: 2025-08-04T14:22:29.156Z
Learnt from: juristr
PR: johnlindquist/worktree-cli#20
File: src/commands/extract.ts:124-127
Timestamp: 2025-08-04T14:22:29.156Z
Learning: The worktree-cli project prioritizes consistency across commands. When implementing new commands like `extract`, developers follow existing patterns from similar commands like `new` to maintain API and implementation consistency.
Applied to files:
build/commands/extract.jsbuild/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/remove.ts : Handle removal of Git worktrees, including support for force deletion, in src/commands/remove.ts.
Applied to files:
build/commands/extract.jsbuild/commands/remove.jssrc/commands/new.tsbuild/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/*.ts : Leverage Execa to execute Git commands and other external processes.
Applied to files:
src/commands/new.ts
🧬 Code graph analysis (10)
build/commands/extract.js (3)
build/utils/git.js (5)
execa(5-5)execa(19-19)execa(68-68)isWorktreeClean(14-40)isMainRepoBare(42-65)build/commands/new.js (6)
folderName(24-24)shortBranchName(31-31)currentDir(32-32)parentDir(33-33)currentDirName(34-34)resolvedPath(38-38)build/config.js (1)
getDefaultEditor(23-25)
build/commands/remove.js (1)
build/utils/git.js (1)
isMainRepoBare(42-65)
build/commands/pr.js (2)
build/utils/git.js (1)
isMainRepoBare(42-65)src/utils/git.ts (1)
isMainRepoBare(44-68)
src/utils/git.ts (1)
build/utils/git.js (4)
getRepoRoot(66-75)execa(5-5)execa(19-19)execa(68-68)
src/commands/new.ts (2)
build/commands/new.js (10)
repoRoot(90-90)setupFilePath(92-92)setupData(93-93)cursorSetupPath(95-95)fallbackSetupPath(102-102)setupContent(114-114)commands(116-116)allowedCommands(126-132)env(133-133)resolvedPath(38-38)src/utils/git.ts (1)
getRepoRoot(70-78)
build/commands/open.js (2)
build/commands/remove.js (7)
targetPath(14-14)isDirectory(16-16)stats(18-18)entries(28-28)currentPath(29-29)fullBranchRef(35-35)shortBranch(36-36)build/config.js (1)
getDefaultEditor(23-25)
build/utils/git.js (1)
src/utils/git.ts (2)
isMainRepoBare(44-68)getRepoRoot(70-78)
build/commands/merge.js (1)
build/utils/git.js (1)
isMainRepoBare(42-65)
build/index.js (2)
build/commands/open.js (1)
openWorktreeHandler(6-87)build/commands/extract.js (1)
extractWorktreeHandler(7-135)
build/commands/new.js (1)
src/utils/git.ts (3)
isWorktreeClean(15-40)isMainRepoBare(44-68)getRepoRoot(70-78)
🔇 Additional comments (18)
build/index.js (3)
10-11: LGTM!The import statements for the new command handlers follow the established pattern and are correctly structured.
56-69: LGTM!The CLI command registrations for
openandextractare well-structured and consistent with existing command patterns in this file. The argument definitions, options, and descriptions are clear and match the functionality described in the PR objectives.
73-84: Confirm backward compatibility of config commands All README.md examples (lines 64–75) still usewt config set editor,wt config get editor, andwt config path; the restructuring doesn’t break existing usage.build/commands/new.js (4)
4-4: LGTM!The import of
readFileis necessary for reading setup configuration files.
7-7: LGTM!The additional utility imports (
isMainRepoBare,getRepoRoot) are correctly added and used for repository validation.
14-18: LGTM!The enhanced error messages provide clear, actionable guidance to users when the main worktree is not clean. The use of colored output (red, yellow, cyan) improves readability and user experience.
74-79: LGTM!The bare repository check is correctly placed before worktree creation and provides clear remediation steps. This prevents users from encountering cryptic Git errors during worktree operations.
build/commands/extract.js (11)
1-6: LGTM!The imports are appropriate and follow the established pattern. All required utilities are correctly imported.
7-21: LGTM!The repository and worktree cleanliness validation follows the same pattern as in
new.js, ensuring consistency across commands. The error messages are clear and actionable.Based on learnings.
22-34: LGTM!The branch determination logic correctly handles both explicit branch names and defaults to the current branch. The detached HEAD check is a good safety measure.
35-45: LGTM!The existing worktree check prevents duplicate worktrees for the same branch. The use of
--porcelainoutput for parsing is the correct approach.
46-59: LGTM!The branch existence verification checks both local and remote branches, which is essential before attempting to create a worktree. The logic correctly handles both scenarios.
85-91: LGTM!The bare repository check is correctly placed and provides clear remediation guidance. This matches the implementation in
new.js.
92-104: LGTM!The worktree creation logic correctly handles both local and remote-only branches. The use of
--trackflag for remote branches is appropriate.
105-109: LGTM!The optional dependency installation follows the same pattern as other commands and uses
stdio: "inherit"to show progress to the user.
110-120: LGTM!The editor opening logic is robust with appropriate error handling. The command continues even if the editor fails to launch, which is the correct behavior.
121-124: LGTM!The success messages are clear and provide useful feedback to the user. The conditional message for dependency installation is a nice touch.
126-134: LGTM!The error handling correctly distinguishes between Error instances and other types of errors, providing appropriate error messages in both cases.
|
@felipeS I'd prefer with this was under a "wt setup" command which represents "new and run scripts". Thoughts? |
Hey John, yes I think that is a better idea. Will do it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (1)
README.md (1)
113-131: Examples include commands that won’t be on an allowlistIf we enforce an allowlist (per PR objectives), cp and echo will be skipped. Update examples to only include allowed commands.
Apply this diff:
@@ "setup-worktree": [ - "npm install", - "cp $ROOT_WORKTREE_PATH/.local.env .local.env", - "echo 'Setup complete'" + "npm install", + "git submodule update --init --recursive" ] @@ [ - "npm install", - "cp $ROOT_WORKTREE_PATH/.local.env .local.env", - "echo 'Setup complete'" + "npm install", + "git submodule update --init --recursive" ]
🧹 Nitpick comments (2)
src/commands/setup.ts (1)
89-96: Use of --checkout flag (optional)Consider honoring options.checkout when the branch is missing locally/remotely:
- If not present and branch missing, print guidance and exit.
- If present, create the new branch with -b.
This aligns with the option description.
src/index.ts (1)
45-66: Remove or implement the unused-c/--checkoutoption
ThesetupWorktreeHandlernever readsoptions.checkout, so the flag is a no-op. Either drop it from the CLI definition or add conditional checkout logic in the handler.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
README.md(2 hunks)build/commands/new.js(2 hunks)build/commands/setup.js(1 hunks)build/index.js(3 hunks)src/commands/setup.ts(1 hunks)src/index.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- build/commands/new.js
🧰 Additional context used
📓 Path-based instructions (2)
src/index.ts
📄 CodeRabbit inference engine (.cursor/rules/project.mdc)
src/index.ts: The main entry point for the CLI is src/index.ts, which sets up CLI commands and orchestrates command handlers.
Utilize Commander for parsing CLI commands and handling options.
Files:
src/index.ts
src/commands/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project.mdc)
src/commands/*.ts: Leverage Execa to execute Git commands and other external processes.
Provide clear, colored console feedback for success and error messages in CLI commands.
Files:
src/commands/setup.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/new.ts : Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/new.ts : Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.
Applied to files:
src/index.tssrc/commands/setup.tsREADME.mdbuild/commands/setup.jsbuild/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/index.ts : The main entry point for the CLI is src/index.ts, which sets up CLI commands and orchestrates command handlers.
Applied to files:
src/index.ts
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/list.ts : Provide functionality to list existing Git worktrees in src/commands/list.ts.
Applied to files:
src/index.tssrc/commands/setup.tsbuild/commands/setup.jsbuild/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/remove.ts : Handle removal of Git worktrees, including support for force deletion, in src/commands/remove.ts.
Applied to files:
src/commands/setup.tsbuild/commands/setup.jsbuild/index.js
🧬 Code graph analysis (4)
src/index.ts (2)
build/commands/setup.js (1)
setupWorktreeHandler(8-223)src/commands/setup.ts (1)
setupWorktreeHandler(9-232)
src/commands/setup.ts (3)
build/commands/setup.js (18)
setupWorktreeHandler(8-223)isClean(13-13)folderName(24-24)shortBranchName(31-31)currentDir(32-32)parentDir(33-33)currentDirName(34-34)resolvedPath(38-38)directoryExists(40-40)localBranches(49-49)remoteBranches(50-50)branchExists(51-51)isGitWorktree(56-56)repoRoot(90-90)commands(116-116)env(146-146)configuredEditor(197-197)editorCommand(198-198)build/commands/pr.js (1)
branchName(20-20)build/commands/new.js (14)
isClean(12-12)folderName(23-23)shortBranchName(30-30)currentDir(31-31)parentDir(32-32)currentDirName(33-33)resolvedPath(37-37)directoryExists(39-39)localBranches(48-48)remoteBranches(49-49)branchExists(50-50)isGitWorktree(55-55)configuredEditor(95-95)editorCommand(96-96)
build/commands/setup.js (1)
src/commands/setup.ts (1)
setupWorktreeHandler(9-232)
build/index.js (3)
build/commands/setup.js (1)
setupWorktreeHandler(8-223)build/commands/open.js (1)
openWorktreeHandler(6-87)build/commands/extract.js (1)
extractWorktreeHandler(7-135)
🔇 Additional comments (5)
src/index.ts (1)
6-6: Wiring OKImporting setupWorktreeHandler looks correct and consistent with the new command.
README.md (1)
33-51: Docs for wt setup look goodSection introduces the new command and options clearly.
build/index.js (3)
4-4: Wiring OKsetupWorktreeHandler import is correct; CLI surface reflects the new command.
27-35: New setup command registered correctlyOptions and description match src/index.ts.
67-79: open/extract commands registration looks goodHandlers and options align with respective implementations.
| const { stdout: localBranches } = await execa("git", ["branch", "--list", branchName]); | ||
| const { stdout: remoteBranches } = await execa("git", ["branch", "-r", "--list", `origin/${branchName}`]); | ||
| const branchExists = !!localBranches || !!remoteBranches; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remote-only branch case breaks: use --track for origin/
If the branch exists only on origin, current logic treats it as “exists” and runs git worktree add , which fails. Mirror extract’s behavior by handling remote-only branches.
Apply this diff:
- const { stdout: localBranches } = await execa("git", ["branch", "--list", branchName]);
- const { stdout: remoteBranches } = await execa("git", ["branch", "-r", "--list", `origin/${branchName}`]);
- const branchExists = !!localBranches || !!remoteBranches;
+ const { stdout: localBranches } = await execa("git", ["branch", "--list", branchName]);
+ const { stdout: remoteBranches } = await execa("git", ["branch", "-r", "--list", `origin/${branchName}`]);
+ const branchExistsLocally = localBranches.trim() !== "";
+ const branchExistsRemotely = remoteBranches.trim() !== "";
@@
- if (!branchExists) {
- console.log(chalk.yellow(`Branch "${branchName}" doesn't exist. Creating new branch with worktree...`));
- // Create a new branch and worktree in one command with -b flag
- await execa("git", ["worktree", "add", "-b", branchName, resolvedPath]);
- } else {
- console.log(chalk.green(`Using existing branch "${branchName}".`));
- await execa("git", ["worktree", "add", resolvedPath, branchName]);
- }
+ if (!branchExistsLocally && branchExistsRemotely) {
+ console.log(chalk.yellow(`Branch "${branchName}" is remote-only. Creating local tracking branch...`));
+ await execa("git", ["worktree", "add", "--track", "-b", branchName, resolvedPath, `origin/${branchName}`]);
+ } else if (!branchExistsLocally && !branchExistsRemotely) {
+ console.log(chalk.yellow(`Branch "${branchName}" doesn't exist locally or remotely. Creating new branch with worktree...`));
+ await execa("git", ["worktree", "add", "-b", branchName, resolvedPath]);
+ } else {
+ console.log(chalk.green(`Using existing branch "${branchName}".`));
+ await execa("git", ["worktree", "add", resolvedPath, branchName]);
+ }Also applies to: 89-96
🤖 Prompt for AI Agents
In src/commands/setup.ts around lines 55-57 (and similarly 89-96), the code
treats a branch that exists only on origin as if it exists locally and later
runs git worktree add <path> <branchName>, which fails; change the branch
detection to distinguish local vs remote: consider branchExistsLocal =
!!localBranches and branchExistsRemote = !!remoteBranches, and when the branch
is remote-only (branchExistsRemote && !branchExistsLocal) create the worktree
using the tracked remote branch (for example invoke git worktree add <path>
--track origin/<branchName> or use git worktree add -b <branchName> <path>
origin/<branchName>), otherwise proceed with the existing local-branch flow;
apply the same distinction/fix for the code at lines 89-96.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnlindquist this doesn't seem like a problem to me, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
|
@coderabbitai generate docstrings |
✅ Actions performedInitiated docstring generation; will generate only if new commits exist. |
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @felipeS. * #22 (comment) The following files were modified: * `src/commands/setup.ts` * `src/utils/git.ts`
|
🎉 This PR is included in version 2.12.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Add
wt setupCommand for Automated Worktree ConfigurationOverview
This PR adds a new
wt setupcommand that creates worktrees and automatically runs setup scripts fromworktrees.jsonor.cursor/worktrees.json. The existingwt newcommand remains unchanged for simple, fast worktree creation.Motivation
When working with multiple worktrees, developers often need to install dependencies, copy configuration files, initialize submodules, or run custom setup scripts. This feature automates these repetitive tasks while maintaining security through a blocklist of dangerous commands.
Command Structure
wt new(Unchanged)wt setup(New)How It Works
File Support
The implementation checks for setup files in this priority order:
.cursor/worktrees.json(Cursor's native format)worktrees.json(generic format)File Formats
Cursor Format (
.cursor/worktrees.json):Generic Format (
worktrees.json):{ "setup-worktree": [ "npm install", "bun install", "cp .env.example .env", "git submodule update --init --recursive", "pnpm build" ] }Execution Details
$ROOT_WORKTREE_PATHenvironment variable provides access to the main repository root--installflag is used)Security Features
Blocklist Approach (Not Allowlist)
Instead of maintaining an allowlist of approved commands, we use a blocklist of dangerous patterns. This provides:
Blocked Patterns
rm -rfand recursive deletionssudoandsu(privilege escalation)chmod,chown(permission changes)curl | sh,wget | sh)dd,mkfs,format)shutdown,reboot,kill -9)/dev/Error Handling
Files Changed
New Files:
src/commands/setup.ts- New command handler with setup script executionbuild/commands/setup.js- Compiled outputModified Files:
src/commands/new.ts- Removed setup script logic (restored original behavior)src/index.ts- Addedsetupcommand definitionREADME.md- Documented bothwt newandwt setupcommandsTesting Instructions
Create a setup file in your repository root:
Test basic worktree creation (no scripts):
Test worktree with setup scripts:
npm installruns automaticallyTest security blocklist:
npm installruns successfullyrm -rfis blocked with warningsudo apt installis blocked with warningTest flexibility (various package managers):
Examples
Before (No Setup Support)
wt new feature/login cd ../myproject-login npm install cp .env.example .env git submodule update --initAfter (Automated Setup)
Checklist
wt newunchanged)newvssetup)Breaking Changes
None - this is a purely additive feature. The
wt newcommand behavior is completely unchanged.Benefits
setup) vs speed (new)Summary by CodeRabbit
New Features
Documentation