@jasonfutch/worktree-manager 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/dist/git/worktree.d.ts +6 -1
- package/dist/git/worktree.js +98 -13
- package/dist/index.js +11 -3
- package/dist/tui/app.d.ts +2 -0
- package/dist/tui/app.js +164 -5
- package/dist/types.d.ts +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A terminal app for managing git worktrees with AI assistance.
|
|
|
6
6
|
|
|
7
7
|
- **TUI Interface** - Full terminal UI built with blessed
|
|
8
8
|
- **Git Worktree Management** - Create, list, and remove worktrees
|
|
9
|
+
- **Branch Flexibility** - Create worktrees from new branches, existing local branches, or remote branches
|
|
9
10
|
- **IDE Integration** - Open worktrees in VS Code, Cursor, Zed, and more
|
|
10
11
|
- **AI Integration** - Launch Claude, Gemini, or Codex in any worktree
|
|
11
12
|
- **Parallel Development** - Work on multiple features simultaneously
|
|
@@ -42,7 +43,7 @@ wtm /path/to/repo
|
|
|
42
43
|
| `↑/k` | Move up |
|
|
43
44
|
| `↓/j` | Move down |
|
|
44
45
|
| `Enter` | Select/Details |
|
|
45
|
-
| `n` | Create new
|
|
46
|
+
| `n` | Create worktree (new or existing branch) |
|
|
46
47
|
| `d` | Delete worktree |
|
|
47
48
|
| `e` | Open in editor (selector) |
|
|
48
49
|
| `t` | Open terminal |
|
|
@@ -51,6 +52,19 @@ wtm /path/to/repo
|
|
|
51
52
|
| `?` | Help |
|
|
52
53
|
| `q` | Quit |
|
|
53
54
|
|
|
55
|
+
### Creating Worktrees
|
|
56
|
+
|
|
57
|
+
Press `n` to create a new worktree. You'll be presented with two options:
|
|
58
|
+
|
|
59
|
+
1. **Create new branch** - Enter a new branch name and select a base branch to create it from
|
|
60
|
+
2. **Use existing branch** - Select from available local or remote branches
|
|
61
|
+
|
|
62
|
+
When using existing branches:
|
|
63
|
+
- Local branches are listed first
|
|
64
|
+
- Remote branches are marked with ⬇ and listed after local branches
|
|
65
|
+
- Selecting a remote branch (e.g., `origin/feature`) automatically creates a local tracking branch
|
|
66
|
+
- Branches already checked out in other worktrees are filtered out
|
|
67
|
+
|
|
54
68
|
### CLI Commands
|
|
55
69
|
|
|
56
70
|
```bash
|
|
@@ -58,11 +72,15 @@ wtm /path/to/repo
|
|
|
58
72
|
wtm list
|
|
59
73
|
wtm list /path/to/repo
|
|
60
74
|
|
|
61
|
-
# Create a new worktree
|
|
75
|
+
# Create a new worktree (creates new branch from base)
|
|
62
76
|
wtm create feature/my-feature
|
|
63
77
|
wtm create feature/my-feature -b main
|
|
64
78
|
wtm create feature/my-feature -p /custom/path
|
|
65
79
|
|
|
80
|
+
# Create worktree from existing branch
|
|
81
|
+
wtm create existing-branch # Uses existing local branch
|
|
82
|
+
wtm create origin/feature -e # Creates local tracking branch from remote
|
|
83
|
+
|
|
66
84
|
# Remove a worktree
|
|
67
85
|
wtm remove feature/my-feature
|
|
68
86
|
wtm remove feature/my-feature --force
|
package/dist/git/worktree.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Worktree, CreateWorktreeOptions } from '../types.js';
|
|
1
|
+
import type { Worktree, CreateWorktreeOptions, BranchInfo } from '../types.js';
|
|
2
2
|
export declare class GitWorktree {
|
|
3
3
|
private repoPath;
|
|
4
4
|
constructor(repoPath: string);
|
|
@@ -44,6 +44,11 @@ export declare class GitWorktree {
|
|
|
44
44
|
* @note This method returns empty array on any error
|
|
45
45
|
*/
|
|
46
46
|
getBranches(): Promise<string[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Get all branches (local and remote) with structured info
|
|
49
|
+
* @returns Array of BranchInfo objects, empty array on error
|
|
50
|
+
*/
|
|
51
|
+
getAllBranches(): Promise<BranchInfo[]>;
|
|
47
52
|
/**
|
|
48
53
|
* Get current branch name
|
|
49
54
|
* @returns Current branch name, or 'unknown' on error
|
package/dist/git/worktree.js
CHANGED
|
@@ -110,13 +110,16 @@ export class GitWorktree {
|
|
|
110
110
|
* Create a new worktree
|
|
111
111
|
*/
|
|
112
112
|
async create(options) {
|
|
113
|
-
const { branch, baseBranch = GIT.DEFAULT_BASE_BRANCH, path: customPath } = options;
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
const { branch, baseBranch = GIT.DEFAULT_BASE_BRANCH, path: customPath, useExisting = false } = options;
|
|
114
|
+
// For existing branches, the branch might be a remote ref like 'origin/feature'
|
|
115
|
+
const isRemoteBranch = branch.includes('/') && !branch.startsWith('refs/');
|
|
116
|
+
const localBranchName = isRemoteBranch ? branch.split('/').slice(1).join('/') : branch;
|
|
117
|
+
// Validate branch name (use local name for validation)
|
|
118
|
+
if (!isValidBranchName(localBranchName)) {
|
|
119
|
+
throw new InvalidBranchError(localBranchName, 'Branch names cannot contain spaces, .., or special characters');
|
|
117
120
|
}
|
|
118
121
|
// Generate worktree path if not provided
|
|
119
|
-
const worktreePath = customPath || path.join(path.dirname(this.repoPath), GIT.WORKTREES_DIR,
|
|
122
|
+
const worktreePath = customPath || path.join(path.dirname(this.repoPath), GIT.WORKTREES_DIR, localBranchName.replace(/\//g, '-'));
|
|
120
123
|
// Validate path doesn't contain traversal attempts
|
|
121
124
|
const repoParent = path.dirname(this.repoPath);
|
|
122
125
|
if (!isPathSafe(worktreePath, repoParent)) {
|
|
@@ -128,20 +131,42 @@ export class GitWorktree {
|
|
|
128
131
|
fs.mkdirSync(parentDir, { recursive: true });
|
|
129
132
|
}
|
|
130
133
|
try {
|
|
131
|
-
// Check if branch exists
|
|
132
|
-
const branchExists = await this.branchExists(branch);
|
|
133
134
|
// Use escaped arguments for safety
|
|
134
135
|
const escapedPath = escapeShellArg(worktreePath);
|
|
135
|
-
const
|
|
136
|
+
const escapedLocalBranch = escapeShellArg(localBranchName);
|
|
136
137
|
const escapedBase = escapeShellArg(baseBranch);
|
|
137
138
|
let command;
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
if (useExisting) {
|
|
140
|
+
if (isRemoteBranch) {
|
|
141
|
+
// For remote branches, create a local tracking branch
|
|
142
|
+
const escapedRemoteBranch = escapeShellArg(branch);
|
|
143
|
+
// Check if a local branch with this name already exists
|
|
144
|
+
const localExists = await this.branchExists(localBranchName);
|
|
145
|
+
if (localExists) {
|
|
146
|
+
// Use the existing local branch
|
|
147
|
+
command = `git worktree add ${escapedPath} ${escapedLocalBranch}`;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Create a new local branch tracking the remote
|
|
151
|
+
command = `git worktree add -b ${escapedLocalBranch} ${escapedPath} ${escapedRemoteBranch}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// For local branches, just check them out
|
|
156
|
+
command = `git worktree add ${escapedPath} ${escapedLocalBranch}`;
|
|
157
|
+
}
|
|
141
158
|
}
|
|
142
159
|
else {
|
|
143
|
-
//
|
|
144
|
-
|
|
160
|
+
// Creating a new branch
|
|
161
|
+
const branchExists = await this.branchExists(branch);
|
|
162
|
+
if (branchExists) {
|
|
163
|
+
// Branch already exists, just check it out
|
|
164
|
+
command = `git worktree add ${escapedPath} ${escapedLocalBranch}`;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Create new branch from base
|
|
168
|
+
command = `git worktree add -b ${escapedLocalBranch} ${escapedPath} ${escapedBase}`;
|
|
169
|
+
}
|
|
145
170
|
}
|
|
146
171
|
await execAsync(command, { cwd: this.repoPath });
|
|
147
172
|
// Get the created worktree info
|
|
@@ -247,6 +272,66 @@ export class GitWorktree {
|
|
|
247
272
|
return [];
|
|
248
273
|
}
|
|
249
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Get all branches (local and remote) with structured info
|
|
277
|
+
* @returns Array of BranchInfo objects, empty array on error
|
|
278
|
+
*/
|
|
279
|
+
async getAllBranches() {
|
|
280
|
+
try {
|
|
281
|
+
// Fetch remote refs first to ensure we have the latest
|
|
282
|
+
await execAsync('git fetch --all --prune', { cwd: this.repoPath }).catch(() => {
|
|
283
|
+
// Ignore fetch errors (e.g., no network)
|
|
284
|
+
});
|
|
285
|
+
const { stdout } = await execAsync('git branch -a --format="%(refname:short)"', {
|
|
286
|
+
cwd: this.repoPath
|
|
287
|
+
});
|
|
288
|
+
const branches = [];
|
|
289
|
+
const lines = stdout.trim().split('\n').filter(b => b);
|
|
290
|
+
// Get list of branches already checked out in worktrees
|
|
291
|
+
const worktrees = await this.list();
|
|
292
|
+
const checkedOutBranches = new Set(worktrees.map(wt => wt.branch).filter(b => b !== 'HEAD (detached)'));
|
|
293
|
+
for (const line of lines) {
|
|
294
|
+
// Skip HEAD pointer
|
|
295
|
+
if (line === 'HEAD' || line.includes('->'))
|
|
296
|
+
continue;
|
|
297
|
+
if (line.startsWith('origin/')) {
|
|
298
|
+
// Remote branch
|
|
299
|
+
const remoteName = 'origin';
|
|
300
|
+
const branchName = line.substring(7); // Remove 'origin/' prefix
|
|
301
|
+
// Skip if this remote branch is already checked out locally
|
|
302
|
+
if (checkedOutBranches.has(branchName))
|
|
303
|
+
continue;
|
|
304
|
+
branches.push({
|
|
305
|
+
name: branchName,
|
|
306
|
+
fullName: line,
|
|
307
|
+
isRemote: true,
|
|
308
|
+
remote: remoteName
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
// Local branch - skip if already checked out in a worktree
|
|
313
|
+
if (checkedOutBranches.has(line))
|
|
314
|
+
continue;
|
|
315
|
+
branches.push({
|
|
316
|
+
name: line,
|
|
317
|
+
fullName: line,
|
|
318
|
+
isRemote: false
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Sort: local branches first, then remote, alphabetically within each group
|
|
323
|
+
branches.sort((a, b) => {
|
|
324
|
+
if (a.isRemote !== b.isRemote) {
|
|
325
|
+
return a.isRemote ? 1 : -1;
|
|
326
|
+
}
|
|
327
|
+
return a.name.localeCompare(b.name);
|
|
328
|
+
});
|
|
329
|
+
return branches;
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
250
335
|
/**
|
|
251
336
|
* Get current branch name
|
|
252
337
|
* @returns Current branch name, or 'unknown' on error
|
package/dist/index.js
CHANGED
|
@@ -93,6 +93,7 @@ program
|
|
|
93
93
|
.argument('[path]', 'Path to git repository', '.')
|
|
94
94
|
.option('-b, --base <branch>', 'Base branch to create from', 'main')
|
|
95
95
|
.option('-p, --path <path>', 'Custom path for the worktree')
|
|
96
|
+
.option('-e, --existing', 'Use existing branch (local or remote)')
|
|
96
97
|
.action(async (branch, repoPath, options) => {
|
|
97
98
|
const resolvedPath = path.resolve(repoPath || '.');
|
|
98
99
|
const repoRoot = GitWorktree.getRepoRoot(resolvedPath);
|
|
@@ -101,12 +102,18 @@ program
|
|
|
101
102
|
process.exit(1);
|
|
102
103
|
}
|
|
103
104
|
const git = new GitWorktree(repoRoot);
|
|
104
|
-
|
|
105
|
+
const isRemote = branch.includes('/') && !branch.startsWith('refs/');
|
|
106
|
+
const displayBranch = isRemote ? branch : branch;
|
|
107
|
+
const message = options.existing && isRemote
|
|
108
|
+
? `Creating worktree from ${displayBranch} (will create local tracking branch)...`
|
|
109
|
+
: `Creating worktree for branch: ${displayBranch}...`;
|
|
110
|
+
console.log(chalk.cyan(message));
|
|
105
111
|
try {
|
|
106
112
|
const wt = await git.create({
|
|
107
113
|
branch,
|
|
108
114
|
baseBranch: options.base,
|
|
109
|
-
path: options.path
|
|
115
|
+
path: options.path,
|
|
116
|
+
useExisting: options.existing
|
|
110
117
|
});
|
|
111
118
|
console.log(chalk.green(`✓ Created worktree: ${wt.path}`));
|
|
112
119
|
}
|
|
@@ -257,11 +264,12 @@ ${chalk.yellow.bold('CLI COMMANDS')}
|
|
|
257
264
|
$ wtm list
|
|
258
265
|
$ wtm list /path/to/repo
|
|
259
266
|
|
|
260
|
-
${chalk.cyan('wtm create')} ${chalk.white('<branch>')} ${chalk.gray('[path] [-b base] [-p path]')}
|
|
267
|
+
${chalk.cyan('wtm create')} ${chalk.white('<branch>')} ${chalk.gray('[path] [-b base] [-p path] [-e]')}
|
|
261
268
|
Create a new worktree
|
|
262
269
|
$ wtm create feature/login
|
|
263
270
|
$ wtm create feature/api -b develop
|
|
264
271
|
$ wtm create bugfix/issue-42 -p /custom/path
|
|
272
|
+
$ wtm create origin/feature -e ${chalk.gray('# Use existing remote branch')}
|
|
265
273
|
|
|
266
274
|
${chalk.cyan('wtm remove')} ${chalk.white('<branch>')} ${chalk.gray('[path] [-f]')}
|
|
267
275
|
Remove a worktree
|
package/dist/tui/app.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export declare class WorktreeManagerTUI {
|
|
|
26
26
|
private isProtectedBranch;
|
|
27
27
|
private showHelp;
|
|
28
28
|
private promptCreateWorktree;
|
|
29
|
+
private promptCreateNewBranch;
|
|
30
|
+
private promptUseExistingBranch;
|
|
29
31
|
private promptDeleteWorktree;
|
|
30
32
|
private openInEditor;
|
|
31
33
|
private openEditorTool;
|
package/dist/tui/app.js
CHANGED
|
@@ -368,8 +368,54 @@ export class WorktreeManagerTUI {
|
|
|
368
368
|
if (this.isModalOpen)
|
|
369
369
|
return;
|
|
370
370
|
this.isModalOpen = true;
|
|
371
|
+
// First, show mode selector
|
|
372
|
+
const modeList = blessed.list({
|
|
373
|
+
parent: this.screen,
|
|
374
|
+
left: 'center',
|
|
375
|
+
top: 'center',
|
|
376
|
+
width: 50,
|
|
377
|
+
height: 8,
|
|
378
|
+
border: { type: 'line' },
|
|
379
|
+
style: {
|
|
380
|
+
border: { fg: 'green' },
|
|
381
|
+
selected: { bg: 'blue', fg: 'white' },
|
|
382
|
+
bg: 'default'
|
|
383
|
+
},
|
|
384
|
+
label: ' Create Worktree ',
|
|
385
|
+
keys: true,
|
|
386
|
+
vi: true,
|
|
387
|
+
items: [
|
|
388
|
+
' Create new branch',
|
|
389
|
+
' Use existing branch'
|
|
390
|
+
]
|
|
391
|
+
});
|
|
392
|
+
modeList.focus();
|
|
393
|
+
this.setStatus(' Select worktree creation mode', 'blue');
|
|
394
|
+
this.screen.render();
|
|
395
|
+
const cleanupModeSelector = () => {
|
|
396
|
+
modeList.destroy();
|
|
397
|
+
this.screen.render();
|
|
398
|
+
};
|
|
399
|
+
modeList.key(['escape'], () => {
|
|
400
|
+
cleanupModeSelector();
|
|
401
|
+
this.isModalOpen = false;
|
|
402
|
+
this.worktreeList.focus();
|
|
403
|
+
this.setStatus(' Cancelled', 'blue');
|
|
404
|
+
});
|
|
405
|
+
modeList.key(['enter'], async () => {
|
|
406
|
+
const selectedIdx = modeList.selected;
|
|
407
|
+
cleanupModeSelector();
|
|
408
|
+
if (selectedIdx === 0) {
|
|
409
|
+
await this.promptCreateNewBranch();
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
await this.promptUseExistingBranch();
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
async promptCreateNewBranch() {
|
|
371
417
|
this.setStatus(' Loading branches...', 'blue');
|
|
372
|
-
// Fetch available branches
|
|
418
|
+
// Fetch available branches for base branch selection
|
|
373
419
|
const branches = await this.git.getBranches();
|
|
374
420
|
if (branches.length === 0) {
|
|
375
421
|
this.setStatus(' Warning: Could not load branches. Using default "main"', 'yellow');
|
|
@@ -386,13 +432,13 @@ export class WorktreeManagerTUI {
|
|
|
386
432
|
border: { fg: 'green' },
|
|
387
433
|
bg: 'default'
|
|
388
434
|
},
|
|
389
|
-
label: ' Create New
|
|
435
|
+
label: ' Create New Branch '
|
|
390
436
|
});
|
|
391
437
|
blessed.text({
|
|
392
438
|
parent: form,
|
|
393
439
|
top: 1,
|
|
394
440
|
left: 2,
|
|
395
|
-
content: '
|
|
441
|
+
content: 'New branch name:',
|
|
396
442
|
style: { fg: 'default' }
|
|
397
443
|
});
|
|
398
444
|
const branchInput = blessed.textbox({
|
|
@@ -444,7 +490,7 @@ export class WorktreeManagerTUI {
|
|
|
444
490
|
style: { fg: 'gray' }
|
|
445
491
|
});
|
|
446
492
|
branchInput.focus();
|
|
447
|
-
this.setStatus('
|
|
493
|
+
this.setStatus(' Enter new branch name', 'blue');
|
|
448
494
|
const submitForm = async () => {
|
|
449
495
|
this.isModalOpen = false;
|
|
450
496
|
const branch = branchInput.getValue().replace(/\t/g, '').trim();
|
|
@@ -456,7 +502,7 @@ export class WorktreeManagerTUI {
|
|
|
456
502
|
if (branch) {
|
|
457
503
|
this.setStatus(` Creating worktree: ${branch} from ${baseBranch}...`, 'blue');
|
|
458
504
|
try {
|
|
459
|
-
await this.git.create({ branch, baseBranch });
|
|
505
|
+
await this.git.create({ branch, baseBranch, useExisting: false });
|
|
460
506
|
await this.refresh();
|
|
461
507
|
this.setStatus(` Created worktree: ${branch}`, 'green');
|
|
462
508
|
}
|
|
@@ -493,6 +539,119 @@ export class WorktreeManagerTUI {
|
|
|
493
539
|
});
|
|
494
540
|
this.screen.render();
|
|
495
541
|
}
|
|
542
|
+
async promptUseExistingBranch() {
|
|
543
|
+
this.setStatus(' Fetching branches...', 'blue');
|
|
544
|
+
this.screen.render();
|
|
545
|
+
// Fetch all branches including remotes
|
|
546
|
+
const allBranches = await this.git.getAllBranches();
|
|
547
|
+
if (allBranches.length === 0) {
|
|
548
|
+
this.isModalOpen = false;
|
|
549
|
+
this.worktreeList.focus();
|
|
550
|
+
this.setStatus(' No available branches found (all may already have worktrees)', 'yellow');
|
|
551
|
+
this.screen.render();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const form = blessed.box({
|
|
555
|
+
parent: this.screen,
|
|
556
|
+
left: 'center',
|
|
557
|
+
top: 'center',
|
|
558
|
+
width: 60,
|
|
559
|
+
height: 20,
|
|
560
|
+
border: { type: 'line' },
|
|
561
|
+
style: {
|
|
562
|
+
border: { fg: 'cyan' },
|
|
563
|
+
bg: 'default'
|
|
564
|
+
},
|
|
565
|
+
label: ' Select Existing Branch '
|
|
566
|
+
});
|
|
567
|
+
blessed.text({
|
|
568
|
+
parent: form,
|
|
569
|
+
top: 1,
|
|
570
|
+
left: 2,
|
|
571
|
+
content: 'Available branches:',
|
|
572
|
+
style: { fg: 'default' }
|
|
573
|
+
});
|
|
574
|
+
// Format branch items for display
|
|
575
|
+
const branchItems = allBranches.map(b => {
|
|
576
|
+
const prefix = b.isRemote ? '{yellow-fg}⬇{/} ' : ' ';
|
|
577
|
+
const label = b.isRemote ? `${b.fullName}` : b.name;
|
|
578
|
+
return `${prefix}${label}`;
|
|
579
|
+
});
|
|
580
|
+
const branchList = blessed.list({
|
|
581
|
+
parent: form,
|
|
582
|
+
top: 3,
|
|
583
|
+
left: 2,
|
|
584
|
+
width: 54,
|
|
585
|
+
height: 12,
|
|
586
|
+
border: { type: 'line' },
|
|
587
|
+
style: {
|
|
588
|
+
border: { fg: 'cyan' },
|
|
589
|
+
selected: { bg: 'blue', fg: 'white' },
|
|
590
|
+
focus: { border: { fg: 'green' } }
|
|
591
|
+
},
|
|
592
|
+
keys: true,
|
|
593
|
+
vi: true,
|
|
594
|
+
mouse: true,
|
|
595
|
+
tags: true,
|
|
596
|
+
items: branchItems,
|
|
597
|
+
scrollbar: {
|
|
598
|
+
ch: '│',
|
|
599
|
+
style: { fg: 'cyan' }
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
blessed.text({
|
|
603
|
+
parent: form,
|
|
604
|
+
top: 16,
|
|
605
|
+
left: 2,
|
|
606
|
+
content: '↑↓/jk Navigate | Enter Select | Esc Cancel | {yellow-fg}⬇{/} = remote',
|
|
607
|
+
tags: true,
|
|
608
|
+
style: { fg: 'gray' }
|
|
609
|
+
});
|
|
610
|
+
branchList.focus();
|
|
611
|
+
this.setStatus(' Select a branch to create worktree from', 'blue');
|
|
612
|
+
this.screen.render();
|
|
613
|
+
const cancelForm = () => {
|
|
614
|
+
this.isModalOpen = false;
|
|
615
|
+
form.destroy();
|
|
616
|
+
this.worktreeList.focus();
|
|
617
|
+
this.setStatus(' Cancelled', 'blue');
|
|
618
|
+
this.screen.render();
|
|
619
|
+
};
|
|
620
|
+
branchList.key(['escape'], cancelForm);
|
|
621
|
+
branchList.key(['enter'], async () => {
|
|
622
|
+
const selectedIdx = branchList.selected;
|
|
623
|
+
const selectedBranch = allBranches[selectedIdx];
|
|
624
|
+
this.isModalOpen = false;
|
|
625
|
+
form.destroy();
|
|
626
|
+
this.worktreeList.focus();
|
|
627
|
+
if (selectedBranch) {
|
|
628
|
+
const displayName = selectedBranch.isRemote ? selectedBranch.fullName : selectedBranch.name;
|
|
629
|
+
const creatingMsg = selectedBranch.isRemote
|
|
630
|
+
? ` Creating worktree from ${displayName} (will create local tracking branch)...`
|
|
631
|
+
: ` Creating worktree for ${displayName}...`;
|
|
632
|
+
this.setStatus(creatingMsg, 'blue');
|
|
633
|
+
this.screen.render();
|
|
634
|
+
try {
|
|
635
|
+
await this.git.create({
|
|
636
|
+
branch: selectedBranch.fullName,
|
|
637
|
+
useExisting: true
|
|
638
|
+
});
|
|
639
|
+
await this.refresh();
|
|
640
|
+
const successMsg = selectedBranch.isRemote
|
|
641
|
+
? ` Created worktree: ${selectedBranch.name} (tracking ${selectedBranch.fullName})`
|
|
642
|
+
: ` Created worktree: ${selectedBranch.name}`;
|
|
643
|
+
this.setStatus(successMsg, 'green');
|
|
644
|
+
}
|
|
645
|
+
catch (error) {
|
|
646
|
+
const message = error instanceof GitCommandError
|
|
647
|
+
? error.getUserMessage()
|
|
648
|
+
: (error instanceof Error ? error.message : String(error));
|
|
649
|
+
this.setStatus(` Error: ${message}`, 'red');
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
this.screen.render();
|
|
653
|
+
});
|
|
654
|
+
}
|
|
496
655
|
async promptDeleteWorktree() {
|
|
497
656
|
if (this.isModalOpen)
|
|
498
657
|
return;
|
package/dist/types.d.ts
CHANGED
|
@@ -34,4 +34,19 @@ export interface CreateWorktreeOptions {
|
|
|
34
34
|
baseBranch?: string;
|
|
35
35
|
/** Custom path for the worktree (default: auto-generated) */
|
|
36
36
|
path?: string;
|
|
37
|
+
/** Whether to use an existing branch instead of creating a new one */
|
|
38
|
+
useExisting?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Represents branch information
|
|
42
|
+
*/
|
|
43
|
+
export interface BranchInfo {
|
|
44
|
+
/** Branch name (without remote prefix for remote branches) */
|
|
45
|
+
name: string;
|
|
46
|
+
/** Full ref name (e.g., 'origin/main' for remote branches) */
|
|
47
|
+
fullName: string;
|
|
48
|
+
/** Whether this is a remote branch */
|
|
49
|
+
isRemote: boolean;
|
|
50
|
+
/** Remote name if this is a remote branch */
|
|
51
|
+
remote?: string;
|
|
37
52
|
}
|