ace-git-worktree 0.19.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.
- checksums.yaml +7 -0
- data/.ace-defaults/git/worktree.yml +250 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-git-worktree.yml +19 -0
- data/CHANGELOG.md +957 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/Rakefile +14 -0
- data/docs/demo/ace-git-worktree-getting-started.gif +0 -0
- data/docs/demo/ace-git-worktree-getting-started.tape.yml +28 -0
- data/docs/demo/fixtures/README.md +3 -0
- data/docs/demo/fixtures/sample.txt +1 -0
- data/docs/getting-started.md +114 -0
- data/docs/handbook.md +38 -0
- data/docs/usage.md +334 -0
- data/exe/ace-git-worktree +24 -0
- data/handbook/agents/worktree.ag.md +189 -0
- data/handbook/skills/as-git-worktree/SKILL.md +27 -0
- data/handbook/skills/as-git-worktree-create/SKILL.md +21 -0
- data/handbook/skills/as-git-worktree-manage/SKILL.md +20 -0
- data/handbook/workflow-instructions/git/worktree-create.wf.md +262 -0
- data/handbook/workflow-instructions/git/worktree-manage.wf.md +384 -0
- data/handbook/workflow-instructions/git/worktree.wf.md +224 -0
- data/lib/ace/git/worktree/atoms/git_command.rb +121 -0
- data/lib/ace/git/worktree/atoms/path_expander.rb +189 -0
- data/lib/ace/git/worktree/atoms/slug_generator.rb +235 -0
- data/lib/ace/git/worktree/atoms/task_id_extractor.rb +91 -0
- data/lib/ace/git/worktree/cli/commands/config.rb +50 -0
- data/lib/ace/git/worktree/cli/commands/create.rb +80 -0
- data/lib/ace/git/worktree/cli/commands/list.rb +76 -0
- data/lib/ace/git/worktree/cli/commands/prune.rb +43 -0
- data/lib/ace/git/worktree/cli/commands/remove.rb +48 -0
- data/lib/ace/git/worktree/cli/commands/shared_helpers.rb +66 -0
- data/lib/ace/git/worktree/cli/commands/switch.rb +44 -0
- data/lib/ace/git/worktree/cli.rb +103 -0
- data/lib/ace/git/worktree/commands/config_command.rb +351 -0
- data/lib/ace/git/worktree/commands/create_command.rb +961 -0
- data/lib/ace/git/worktree/commands/list_command.rb +247 -0
- data/lib/ace/git/worktree/commands/prune_command.rb +260 -0
- data/lib/ace/git/worktree/commands/remove_command.rb +522 -0
- data/lib/ace/git/worktree/commands/switch_command.rb +249 -0
- data/lib/ace/git/worktree/configuration.rb +167 -0
- data/lib/ace/git/worktree/models/worktree_config.rb +502 -0
- data/lib/ace/git/worktree/models/worktree_info.rb +303 -0
- data/lib/ace/git/worktree/models/worktree_metadata.rb +294 -0
- data/lib/ace/git/worktree/molecules/config_loader.rb +125 -0
- data/lib/ace/git/worktree/molecules/current_task_linker.rb +136 -0
- data/lib/ace/git/worktree/molecules/hook_executor.rb +361 -0
- data/lib/ace/git/worktree/molecules/parent_task_resolver.rb +186 -0
- data/lib/ace/git/worktree/molecules/pr_creator.rb +253 -0
- data/lib/ace/git/worktree/molecules/task_committer.rb +329 -0
- data/lib/ace/git/worktree/molecules/task_fetcher.rb +244 -0
- data/lib/ace/git/worktree/molecules/task_pusher.rb +183 -0
- data/lib/ace/git/worktree/molecules/task_status_updater.rb +447 -0
- data/lib/ace/git/worktree/molecules/worktree_creator.rb +832 -0
- data/lib/ace/git/worktree/molecules/worktree_lister.rb +337 -0
- data/lib/ace/git/worktree/molecules/worktree_remover.rb +416 -0
- data/lib/ace/git/worktree/organisms/task_worktree_orchestrator.rb +906 -0
- data/lib/ace/git/worktree/organisms/worktree_manager.rb +714 -0
- data/lib/ace/git/worktree/version.rb +9 -0
- data/lib/ace/git/worktree.rb +215 -0
- metadata +218 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Git
|
|
5
|
+
module Worktree
|
|
6
|
+
module Commands
|
|
7
|
+
# List command
|
|
8
|
+
#
|
|
9
|
+
# Lists worktrees with various formatting and filtering options.
|
|
10
|
+
# Supports task-aware listing and search capabilities.
|
|
11
|
+
#
|
|
12
|
+
# @example List all worktrees
|
|
13
|
+
# ListCommand.new.run([])
|
|
14
|
+
#
|
|
15
|
+
# @example List with JSON output
|
|
16
|
+
# ListCommand.new.run(["--format", "json"])
|
|
17
|
+
#
|
|
18
|
+
# @example List only task-associated worktrees
|
|
19
|
+
# ListCommand.new.run(["--task-associated"])
|
|
20
|
+
class ListCommand
|
|
21
|
+
# Initialize a new ListCommand
|
|
22
|
+
def initialize
|
|
23
|
+
@manager = Organisms::WorktreeManager.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Run the list command
|
|
27
|
+
#
|
|
28
|
+
# @param args [Array<String>] Command arguments
|
|
29
|
+
# @return [Integer] Exit code (0 for success, 1 for error)
|
|
30
|
+
def run(args = [])
|
|
31
|
+
options = parse_arguments(args)
|
|
32
|
+
return show_help if options[:help]
|
|
33
|
+
|
|
34
|
+
validate_options(options)
|
|
35
|
+
|
|
36
|
+
# Convert format to symbol for WorktreeLister compatibility
|
|
37
|
+
options[:format] = options[:format].to_sym if options[:format]
|
|
38
|
+
|
|
39
|
+
result = @manager.list_all(options)
|
|
40
|
+
|
|
41
|
+
if result[:success]
|
|
42
|
+
display_list_result(result, options)
|
|
43
|
+
0
|
|
44
|
+
else
|
|
45
|
+
puts "Failed to list worktrees: #{result[:error]}"
|
|
46
|
+
1
|
|
47
|
+
end
|
|
48
|
+
rescue ArgumentError => e
|
|
49
|
+
puts "Error: #{e.message}"
|
|
50
|
+
puts
|
|
51
|
+
show_help
|
|
52
|
+
1
|
|
53
|
+
rescue => e
|
|
54
|
+
puts "Error: #{e.message}"
|
|
55
|
+
1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Show help for the list command
|
|
59
|
+
#
|
|
60
|
+
# @return [Integer] Exit code
|
|
61
|
+
def show_help
|
|
62
|
+
puts <<~HELP
|
|
63
|
+
ace-git-worktree list - List worktrees
|
|
64
|
+
|
|
65
|
+
USAGE:
|
|
66
|
+
ace-git-worktree list [OPTIONS]
|
|
67
|
+
|
|
68
|
+
OUTPUT FORMATS:
|
|
69
|
+
--format <format> Output format: table, json, simple (default: table)
|
|
70
|
+
--show-tasks Include task associations
|
|
71
|
+
|
|
72
|
+
FILTERING:
|
|
73
|
+
--task-associated Show only task-associated worktrees
|
|
74
|
+
--no-task-associated Show only non-task worktrees
|
|
75
|
+
--usable Show only usable worktrees
|
|
76
|
+
--no-usable Show only unusable worktrees
|
|
77
|
+
--search <pattern> Filter by branch name pattern
|
|
78
|
+
|
|
79
|
+
EXAMPLES:
|
|
80
|
+
# List all worktrees in table format
|
|
81
|
+
ace-git-worktree list
|
|
82
|
+
|
|
83
|
+
# List with task associations in JSON format
|
|
84
|
+
ace-git-worktree list --show-tasks --format json
|
|
85
|
+
|
|
86
|
+
# List only task-associated worktrees
|
|
87
|
+
ace-git-worktree list --task-associated
|
|
88
|
+
|
|
89
|
+
# Search for worktrees with "auth" in branch name
|
|
90
|
+
ace-git-worktree list --search auth
|
|
91
|
+
|
|
92
|
+
# List only usable worktrees
|
|
93
|
+
ace-git-worktree list --usable
|
|
94
|
+
|
|
95
|
+
OUTPUT:
|
|
96
|
+
Table format columns:
|
|
97
|
+
- Task: Task ID (or - for non-task worktrees)
|
|
98
|
+
- Branch: Git branch name
|
|
99
|
+
- Path: Worktree directory path
|
|
100
|
+
- Status: worktree status (task, normal, bare, detached, etc.)
|
|
101
|
+
|
|
102
|
+
JSON format includes full worktree details and metadata.
|
|
103
|
+
HELP
|
|
104
|
+
0
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# Parse command line arguments
|
|
110
|
+
#
|
|
111
|
+
# @param args [Array<String>] Command arguments
|
|
112
|
+
# @return [Hash] Parsed options
|
|
113
|
+
def parse_arguments(args)
|
|
114
|
+
options = {
|
|
115
|
+
format: "table",
|
|
116
|
+
show_tasks: false,
|
|
117
|
+
task_associated: nil,
|
|
118
|
+
usable: nil,
|
|
119
|
+
search: nil,
|
|
120
|
+
help: false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
i = 0
|
|
124
|
+
while i < args.length
|
|
125
|
+
arg = args[i]
|
|
126
|
+
|
|
127
|
+
case arg
|
|
128
|
+
when "--format"
|
|
129
|
+
i += 1
|
|
130
|
+
format = args[i]&.downcase
|
|
131
|
+
if %w[table json simple].include?(format)
|
|
132
|
+
options[:format] = format
|
|
133
|
+
else
|
|
134
|
+
raise ArgumentError, "Invalid format: #{format}. Use: table, json, simple"
|
|
135
|
+
end
|
|
136
|
+
when "--show-tasks"
|
|
137
|
+
options[:show_tasks] = true
|
|
138
|
+
when "--task-associated"
|
|
139
|
+
options[:task_associated] = true
|
|
140
|
+
when "--no-task-associated"
|
|
141
|
+
options[:task_associated] = false
|
|
142
|
+
when "--usable"
|
|
143
|
+
options[:usable] = true
|
|
144
|
+
when "--no-usable"
|
|
145
|
+
options[:usable] = false
|
|
146
|
+
when "--search"
|
|
147
|
+
i += 1
|
|
148
|
+
options[:search] = args[i]
|
|
149
|
+
when "--help", "-h"
|
|
150
|
+
options[:help] = true
|
|
151
|
+
when /^--/
|
|
152
|
+
raise ArgumentError, "Unknown option: #{arg}"
|
|
153
|
+
else
|
|
154
|
+
raise ArgumentError, "Unexpected argument: #{arg}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
i += 1
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
options
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Validate parsed options
|
|
164
|
+
#
|
|
165
|
+
# @param options [Hash] Parsed options
|
|
166
|
+
def validate_options(options)
|
|
167
|
+
if options[:search] && options[:search].empty?
|
|
168
|
+
raise ArgumentError, "Search pattern cannot be empty"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
if options[:format] && !%w[table json simple].include?(options[:format])
|
|
172
|
+
raise ArgumentError, "Invalid format: #{options[:format]}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Security validation for search patterns
|
|
176
|
+
if options[:search] && contains_dangerous_patterns?(options[:search])
|
|
177
|
+
raise ArgumentError, "Search pattern contains potentially dangerous characters"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Check if a string contains dangerous patterns
|
|
182
|
+
#
|
|
183
|
+
# @param value [String] Value to check
|
|
184
|
+
# @return [Boolean] true if dangerous patterns found
|
|
185
|
+
def contains_dangerous_patterns?(value)
|
|
186
|
+
return false if value.nil?
|
|
187
|
+
|
|
188
|
+
dangerous_patterns = [
|
|
189
|
+
/;/, # Command separator
|
|
190
|
+
/\|/, # Pipe
|
|
191
|
+
/`/, # Backtick command substitution
|
|
192
|
+
/\$\(/, # Command substitution
|
|
193
|
+
/\.\.\//, # Path traversal
|
|
194
|
+
/&&/, # AND operator
|
|
195
|
+
/\|\|/ # OR operator
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
dangerous_patterns.any? { |pattern| value.match?(pattern) }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Display list result
|
|
202
|
+
#
|
|
203
|
+
# @param result [Hash] List result
|
|
204
|
+
# @param options [Hash] Command options
|
|
205
|
+
def display_list_result(result, options)
|
|
206
|
+
if result[:worktrees].empty?
|
|
207
|
+
puts "No worktrees found."
|
|
208
|
+
return
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Display the formatted output
|
|
212
|
+
puts result[:formatted_output]
|
|
213
|
+
|
|
214
|
+
# Display summary if requested or if not JSON format
|
|
215
|
+
if options[:format] != :json
|
|
216
|
+
display_summary(result, options)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Display summary information
|
|
221
|
+
#
|
|
222
|
+
# @param result [Hash] List result
|
|
223
|
+
# @param options [Hash] Command options
|
|
224
|
+
def display_summary(result, options)
|
|
225
|
+
stats = result[:statistics]
|
|
226
|
+
puts "\nSummary:"
|
|
227
|
+
puts " Total worktrees: #{stats[:total]}"
|
|
228
|
+
puts " Task-associated: #{stats[:task_associated]}"
|
|
229
|
+
puts " Usable: #{stats[:usable]}"
|
|
230
|
+
|
|
231
|
+
if options[:show_tasks] && stats[:task_ids].any?
|
|
232
|
+
puts " Tasks with worktrees: #{stats[:task_ids].join(", ")}"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
if stats[:branches].any?
|
|
236
|
+
puts " Branches: #{stats[:branches].join(", ")}"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Show active worktrees count
|
|
240
|
+
active_count = result[:worktrees].count { |wt| wt.exists? && wt.usable? }
|
|
241
|
+
puts " Active worktrees: #{active_count}"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Git
|
|
5
|
+
module Worktree
|
|
6
|
+
module Commands
|
|
7
|
+
# Prune command
|
|
8
|
+
#
|
|
9
|
+
# Cleans up git metadata for deleted worktrees and removes orphaned
|
|
10
|
+
# worktree directories that are no longer tracked by git.
|
|
11
|
+
#
|
|
12
|
+
# @example Prune deleted worktrees
|
|
13
|
+
# PruneCommand.new.run([])
|
|
14
|
+
#
|
|
15
|
+
# @example Prune with directory cleanup
|
|
16
|
+
# PruneCommand.new.run(["--cleanup-directories"])
|
|
17
|
+
class PruneCommand
|
|
18
|
+
# Initialize a new PruneCommand
|
|
19
|
+
def initialize
|
|
20
|
+
@manager = Organisms::WorktreeManager.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Run the prune command
|
|
24
|
+
#
|
|
25
|
+
# @param args [Array<String>] Command arguments
|
|
26
|
+
# @return [Integer] Exit code (0 for success, 1 for error)
|
|
27
|
+
def run(args = [])
|
|
28
|
+
options = parse_arguments(args)
|
|
29
|
+
return show_help if options[:help]
|
|
30
|
+
|
|
31
|
+
validate_options(options)
|
|
32
|
+
|
|
33
|
+
result = @manager.prune
|
|
34
|
+
|
|
35
|
+
if result[:success]
|
|
36
|
+
display_prune_result(result, options)
|
|
37
|
+
|
|
38
|
+
# Additional directory cleanup if requested
|
|
39
|
+
if options[:cleanup_directories]
|
|
40
|
+
cleanup_orphaned_directories(options)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
0
|
|
44
|
+
else
|
|
45
|
+
puts "Failed to prune worktrees: #{result[:error]}"
|
|
46
|
+
1
|
|
47
|
+
end
|
|
48
|
+
rescue ArgumentError => e
|
|
49
|
+
puts "Error: #{e.message}"
|
|
50
|
+
puts
|
|
51
|
+
show_help
|
|
52
|
+
1
|
|
53
|
+
rescue => e
|
|
54
|
+
puts "Error: #{e.message}"
|
|
55
|
+
1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Show help for the prune command
|
|
59
|
+
#
|
|
60
|
+
# @return [Integer] Exit code
|
|
61
|
+
def show_help
|
|
62
|
+
puts <<~HELP
|
|
63
|
+
ace-git-worktree prune - Clean up deleted worktrees
|
|
64
|
+
|
|
65
|
+
USAGE:
|
|
66
|
+
ace-git-worktree prune [OPTIONS]
|
|
67
|
+
|
|
68
|
+
OPTIONS:
|
|
69
|
+
--dry-run Show what would be pruned without pruning
|
|
70
|
+
--cleanup-directories Remove orphaned worktree directories
|
|
71
|
+
--verbose, -v Show detailed pruning information
|
|
72
|
+
--help, -h Show this help message
|
|
73
|
+
|
|
74
|
+
EXAMPLES:
|
|
75
|
+
# Prune deleted worktrees (git metadata cleanup only)
|
|
76
|
+
ace-git-worktree prune
|
|
77
|
+
|
|
78
|
+
# Dry run to see what would be pruned
|
|
79
|
+
ace-git-worktree prune --dry-run
|
|
80
|
+
|
|
81
|
+
# Prune and cleanup orphaned directories
|
|
82
|
+
ace-git-worktree prune --cleanup-directories
|
|
83
|
+
|
|
84
|
+
# Verbose output
|
|
85
|
+
ace-git-worktree prune --verbose
|
|
86
|
+
|
|
87
|
+
WHAT IT DOES:
|
|
88
|
+
1. Prunes git worktree metadata for deleted worktrees
|
|
89
|
+
2. Removes stale worktree entries from git's tracking
|
|
90
|
+
3. Optionally removes orphaned worktree directories
|
|
91
|
+
4. Reports what was cleaned up
|
|
92
|
+
|
|
93
|
+
SAFETY:
|
|
94
|
+
• Only removes worktrees that are no longer tracked by git
|
|
95
|
+
• Does not affect active worktrees or current worktree
|
|
96
|
+
• Directory cleanup is optional and requires explicit flag
|
|
97
|
+
• Dry run available to preview changes
|
|
98
|
+
|
|
99
|
+
CONFIGURATION:
|
|
100
|
+
Pruning behavior can be configured in .ace/git/worktree.yml:
|
|
101
|
+
- cleanup.on_delete: Automatic cleanup on branch deletion
|
|
102
|
+
- cleanup.on_merge: Automatic cleanup on branch merge
|
|
103
|
+
HELP
|
|
104
|
+
0
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# Parse command line arguments
|
|
110
|
+
#
|
|
111
|
+
# @param args [Array<String>] Command arguments
|
|
112
|
+
# @return [Hash] Parsed options
|
|
113
|
+
def parse_arguments(args)
|
|
114
|
+
options = {
|
|
115
|
+
dry_run: false,
|
|
116
|
+
cleanup_directories: false,
|
|
117
|
+
force: false,
|
|
118
|
+
verbose: false,
|
|
119
|
+
help: false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
i = 0
|
|
123
|
+
while i < args.length
|
|
124
|
+
arg = args[i]
|
|
125
|
+
|
|
126
|
+
case arg
|
|
127
|
+
when "--dry-run"
|
|
128
|
+
options[:dry_run] = true
|
|
129
|
+
when "--cleanup-directories"
|
|
130
|
+
options[:cleanup_directories] = true
|
|
131
|
+
when "--force"
|
|
132
|
+
options[:force] = true
|
|
133
|
+
when "--verbose", "-v"
|
|
134
|
+
options[:verbose] = true
|
|
135
|
+
when "--help", "-h"
|
|
136
|
+
options[:help] = true
|
|
137
|
+
when /^--/
|
|
138
|
+
raise ArgumentError, "Unknown option: #{arg}"
|
|
139
|
+
else
|
|
140
|
+
raise ArgumentError, "Unexpected argument: #{arg}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
i += 1
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
options
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Validate parsed options
|
|
150
|
+
#
|
|
151
|
+
# @param options [Hash] Parsed options
|
|
152
|
+
def validate_options(options)
|
|
153
|
+
# No specific validation needed for prune command
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Display prune result
|
|
157
|
+
#
|
|
158
|
+
# @param result [Hash] Prune result
|
|
159
|
+
# @param options [Hash] Command options
|
|
160
|
+
def display_prune_result(result, options)
|
|
161
|
+
if result[:pruned_count] && result[:pruned_count] > 0
|
|
162
|
+
puts "Pruned #{result[:pruned_count]} worktree(s) successfully."
|
|
163
|
+
|
|
164
|
+
if options[:verbose] && result[:output]
|
|
165
|
+
puts "\nPruned worktrees:"
|
|
166
|
+
result[:output].split("\n").each do |line|
|
|
167
|
+
next unless line.include?("Pruning worktree")
|
|
168
|
+
path = line.match(/Pruning worktree (.+)$/)[1]
|
|
169
|
+
puts " ✓ #{path}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
else
|
|
173
|
+
puts "No worktrees to prune. Git metadata is clean."
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if options[:dry_run]
|
|
177
|
+
puts "\nDRY RUN - No changes were made."
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Clean up orphaned directories
|
|
182
|
+
#
|
|
183
|
+
# @param options [Hash] Command options
|
|
184
|
+
def cleanup_orphaned_directories(options)
|
|
185
|
+
puts "\nChecking for orphaned worktree directories..."
|
|
186
|
+
|
|
187
|
+
# Get current worktree root from configuration
|
|
188
|
+
config = @manager.configuration
|
|
189
|
+
worktree_root = config.absolute_root_path
|
|
190
|
+
|
|
191
|
+
unless Dir.exist?(worktree_root)
|
|
192
|
+
puts "Worktree root directory does not exist: #{worktree_root}"
|
|
193
|
+
return
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Get currently tracked worktrees
|
|
197
|
+
list_result = @manager.list_all
|
|
198
|
+
return unless list_result[:success]
|
|
199
|
+
|
|
200
|
+
tracked_paths = list_result[:worktrees].map { |wt| File.expand_path(wt.path) }
|
|
201
|
+
|
|
202
|
+
# Find directories in worktree root that are not tracked
|
|
203
|
+
orphaned_count = 0
|
|
204
|
+
Dir.glob(File.join(worktree_root, "*")).each do |path|
|
|
205
|
+
next unless File.directory?(path)
|
|
206
|
+
next if tracked_paths.include?(File.expand_path(path))
|
|
207
|
+
|
|
208
|
+
# Check if this looks like a worktree directory
|
|
209
|
+
if looks_like_worktree_directory?(path)
|
|
210
|
+
if options[:dry_run]
|
|
211
|
+
puts " Would remove orphaned directory: #{File.basename(path)}"
|
|
212
|
+
orphaned_count += 1
|
|
213
|
+
else
|
|
214
|
+
if options[:verbose]
|
|
215
|
+
puts " Removing orphaned directory: #{File.basename(path)}"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
begin
|
|
219
|
+
FileUtils.rm_rf(path)
|
|
220
|
+
orphaned_count += 1
|
|
221
|
+
rescue => e
|
|
222
|
+
puts " Failed to remove #{path}: #{e.message}"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
if orphaned_count > 0
|
|
229
|
+
action = options[:dry_run] ? "Would remove" : "Removed"
|
|
230
|
+
puts "#{action} #{orphaned_count} orphaned director(y/ies)."
|
|
231
|
+
else
|
|
232
|
+
puts "No orphaned directories found."
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Check if directory looks like a worktree
|
|
237
|
+
#
|
|
238
|
+
# @param path [String] Directory path
|
|
239
|
+
# @return [Boolean] true if it looks like a worktree directory
|
|
240
|
+
def looks_like_worktree_directory?(path)
|
|
241
|
+
# Check for common indicators of a worktree
|
|
242
|
+
indicators = [
|
|
243
|
+
File.join(path, ".git"), # Git directory (file)
|
|
244
|
+
File.join(path, "mise.toml"), # Mise configuration
|
|
245
|
+
File.join(path, ".mise"), # Mise directory
|
|
246
|
+
File.join(path, "package.json"), # Node.js project
|
|
247
|
+
File.join(path, "Gemfile"), # Ruby project
|
|
248
|
+
File.join(path, "Cargo.toml") # Rust project
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
# Check if any indicators exist
|
|
252
|
+
indicators.any? { |indicator| File.exist?(indicator) }
|
|
253
|
+
rescue
|
|
254
|
+
false
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|