-
Notifications
You must be signed in to change notification settings - Fork 22
feat: Add cd command to navigate to worktree directories #45
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import { execa } from "execa"; | ||
| import chalk from "chalk"; | ||
| import { stat } from "node:fs/promises"; | ||
| import { resolve } from "node:path"; | ||
| import { findWorktreeByBranch, findWorktreeByPath } from "../utils/git.js"; | ||
| import { selectWorktree } from "../utils/tui.js"; | ||
| export async function cdWorktreeHandler(pathOrBranch = "") { | ||
| try { | ||
| await execa("git", ["rev-parse", "--is-inside-work-tree"]); | ||
| let targetWorktree = null; | ||
| if (!pathOrBranch) { | ||
| const selected = await selectWorktree({ | ||
| message: "Select a worktree to navigate to", | ||
| excludeMain: false, | ||
| }); | ||
| if (!selected || Array.isArray(selected)) { | ||
| console.error(chalk.yellow("No worktree selected.")); | ||
| process.exit(0); | ||
| } | ||
| targetWorktree = selected; | ||
| } | ||
| else { | ||
| // Try to find by path first | ||
| try { | ||
| const stats = await stat(pathOrBranch); | ||
| if (stats.isDirectory()) { | ||
| targetWorktree = await findWorktreeByPath(pathOrBranch); | ||
| if (!targetWorktree) { | ||
| try { | ||
| await stat(resolve(pathOrBranch, ".git")); | ||
| targetWorktree = { | ||
| path: resolve(pathOrBranch), | ||
| head: '', | ||
| branch: null, | ||
| detached: false, | ||
| locked: false, | ||
| prunable: false, | ||
| isMain: false, | ||
| bare: false, | ||
| }; | ||
| } | ||
| catch { | ||
| console.error(chalk.red(`The path "${pathOrBranch}" exists but is not a git worktree.`)); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| catch { | ||
| // Not a valid path, try as branch name | ||
| } | ||
| if (!targetWorktree) { | ||
| targetWorktree = await findWorktreeByBranch(pathOrBranch); | ||
| if (!targetWorktree) { | ||
| console.error(chalk.red(`Could not find a worktree for branch "${pathOrBranch}".`)); | ||
| console.error(chalk.yellow("Use 'wt list' to see existing worktrees, or run 'wt cd' without arguments to select interactively.")); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| } | ||
| const targetPath = targetWorktree.path; | ||
| // Verify the target path exists | ||
| try { | ||
| await stat(targetPath); | ||
| } | ||
| catch { | ||
| console.error(chalk.red(`The worktree path "${targetPath}" no longer exists.`)); | ||
| console.error(chalk.yellow("The worktree may have been removed. Run 'git worktree prune' to clean up.")); | ||
| process.exit(1); | ||
| } | ||
| // Shell-escape the path by wrapping in single quotes | ||
| const escapedPath = "'" + targetPath.replace(/'/g, "'\\''") + "'"; | ||
| const cdCommand = `cd ${escapedPath}`; | ||
| // Copy cd command to clipboard (cross-platform) | ||
| let clipboardCmd; | ||
| let clipboardArgs; | ||
| if (process.platform === "darwin") { | ||
| clipboardCmd = "pbcopy"; | ||
| clipboardArgs = []; | ||
| } | ||
| else if (process.platform === "win32") { | ||
| clipboardCmd = "clip.exe"; | ||
| clipboardArgs = []; | ||
| } | ||
| else { | ||
| clipboardCmd = "xclip"; | ||
| clipboardArgs = ["-selection", "clipboard"]; | ||
| } | ||
| try { | ||
| await execa(clipboardCmd, clipboardArgs, { input: cdCommand }); | ||
| console.log(chalk.green(`Copied to clipboard: ${cdCommand}`)); | ||
| const pasteHint = process.platform === "darwin" ? "Cmd+V" : "Ctrl+V"; | ||
| console.log(chalk.gray(`Paste with ${pasteHint} and press Enter to navigate.`)); | ||
| } | ||
| catch { | ||
| // Fallback if clipboard command is unavailable | ||
| console.error(chalk.yellow("Clipboard unavailable. Copy and run the command below:")); | ||
| process.stdout.write(cdCommand + "\n"); | ||
| } | ||
| } | ||
| catch (error) { | ||
| if (error instanceof Error) { | ||
| console.error(chalk.red("Failed to resolve worktree:"), error.message); | ||
| } | ||
| else { | ||
| console.error(chalk.red("Failed to resolve worktree:"), error); | ||
| } | ||
| process.exit(1); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| import { execa } from "execa"; | ||
| import chalk from "chalk"; | ||
| import { stat } from "node:fs/promises"; | ||
| import { resolve } from "node:path"; | ||
| import { findWorktreeByBranch, findWorktreeByPath, WorktreeInfo } from "../utils/git.js"; | ||
| import { selectWorktree } from "../utils/tui.js"; | ||
|
|
||
| export async function cdWorktreeHandler(pathOrBranch: string = "") { | ||
| try { | ||
| await execa("git", ["rev-parse", "--is-inside-work-tree"]); | ||
|
|
||
| let targetWorktree: WorktreeInfo | null = null; | ||
|
|
||
| if (!pathOrBranch) { | ||
| const selected = await selectWorktree({ | ||
| message: "Select a worktree to navigate to", | ||
| excludeMain: false, | ||
| }); | ||
|
|
||
| if (!selected || Array.isArray(selected)) { | ||
| console.error(chalk.yellow("No worktree selected.")); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| targetWorktree = selected; | ||
| } else { | ||
| // Try to find by path first | ||
| try { | ||
| const stats = await stat(pathOrBranch); | ||
| if (stats.isDirectory()) { | ||
| targetWorktree = await findWorktreeByPath(pathOrBranch); | ||
| if (!targetWorktree) { | ||
| try { | ||
| await stat(resolve(pathOrBranch, ".git")); | ||
| targetWorktree = { | ||
| path: resolve(pathOrBranch), | ||
| head: '', | ||
| branch: null, | ||
| detached: false, | ||
| locked: false, | ||
| prunable: false, | ||
| isMain: false, | ||
| bare: false, | ||
| }; | ||
| } catch { | ||
| console.error(chalk.red(`The path "${pathOrBranch}" exists but is not a git worktree.`)); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| } | ||
| } catch { | ||
| // Not a valid path, try as branch name | ||
| } | ||
|
|
||
| if (!targetWorktree) { | ||
| targetWorktree = await findWorktreeByBranch(pathOrBranch); | ||
| if (!targetWorktree) { | ||
| console.error(chalk.red(`Could not find a worktree for branch "${pathOrBranch}".`)); | ||
| console.error(chalk.yellow("Use 'wt list' to see existing worktrees, or run 'wt cd' without arguments to select interactively.")); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const targetPath = targetWorktree.path; | ||
|
|
||
| // Verify the target path exists | ||
| try { | ||
| await stat(targetPath); | ||
| } catch { | ||
| console.error(chalk.red(`The worktree path "${targetPath}" no longer exists.`)); | ||
| console.error(chalk.yellow("The worktree may have been removed. Run 'git worktree prune' to clean up.")); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Shell-escape the path by wrapping in single quotes | ||
| const escapedPath = "'" + targetPath.replace(/'/g, "'\\''") + "'"; | ||
| const cdCommand = `cd ${escapedPath}`; | ||
|
Comment on lines
+76
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cd src/commands && head -n 95 cd.ts | tail -n 35Repository: johnlindquist/worktree-cli Length of output: 1281 🏁 Script executed: cd src/commands && sed -n '86,88p' cd.tsRepository: johnlindquist/worktree-cli Length of output: 191 🏁 Script executed: cd src/commands && sed -n '78,95p' cd.tsRepository: johnlindquist/worktree-cli Length of output: 692 🏁 Script executed: cd src/commands && wc -l cd.ts && cat -n cd.tsRepository: johnlindquist/worktree-cli Length of output: 5286 🌐 Web query:
💡 Result: In
To handle spaces / special characters in a path in
Escaping note (cmd parser):
Bottom line: for Sources: Citations:
🌐 Web query:
💡 Result: In Windows PowerShell / PowerShell on Windows,
Sources: about_Quoting_Rules[1], Set-Location ( [1] (learn.microsoft.com) Citations:
Adapt shell-escaping strategy to platform-specific requirements. The current POSIX single-quote escaping (lines 76–77) fails on Windows
🤖 Prompt for AI Agents
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 1795
const escapedPath = "'" + targetPath.replace(/'/g, "'\\''") + "'";
const cdCommand = `cd ${escapedPath}`;The platform-specific escaping logic hasn't been added yet. The code needs to branch based on the shell/platform (similar to how it detects the platform for clipboard operations on lines 81-90) to generate the correct
Have you pushed the changes yet, or would you like help implementing the platform-specific escaping logic? 🧠 Learnings used |
||
|
|
||
| // Copy cd command to clipboard (cross-platform) | ||
| let clipboardCmd: string; | ||
| let clipboardArgs: string[]; | ||
| if (process.platform === "darwin") { | ||
| clipboardCmd = "pbcopy"; | ||
| clipboardArgs = []; | ||
| } else if (process.platform === "win32") { | ||
| clipboardCmd = "clip.exe"; | ||
| clipboardArgs = []; | ||
| } else { | ||
| clipboardCmd = "xclip"; | ||
| clipboardArgs = ["-selection", "clipboard"]; | ||
| } | ||
|
|
||
| try { | ||
| await execa(clipboardCmd, clipboardArgs, { input: cdCommand }); | ||
| console.log(chalk.green(`Copied to clipboard: ${cdCommand}`)); | ||
| const pasteHint = process.platform === "darwin" ? "Cmd+V" : "Ctrl+V"; | ||
| console.log(chalk.gray(`Paste with ${pasteHint} and press Enter to navigate.`)); | ||
| } catch { | ||
| // Fallback if clipboard command is unavailable | ||
| console.error(chalk.yellow("Clipboard unavailable. Copy and run the command below:")); | ||
| process.stdout.write(cdCommand + "\n"); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| console.error(chalk.red("Failed to resolve worktree:"), error.message); | ||
| } else { | ||
| console.error(chalk.red("Failed to resolve worktree:"), error); | ||
| } | ||
| process.exit(1); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.