Class: Ace::Git::Worktree::Organisms::WorktreeManager

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/git/worktree/organisms/worktree_manager.rb

Overview

Worktree manager organism

High-level manager for all worktree operations, providing a unified interface for creating, listing, switching between, removing, and managing worktrees. Integrates both task-aware and traditional worktree operations.

Examples:

Create a worktree

manager = WorktreeManager.new
result = manager.create("feature-branch")

Create a task-aware worktree

result = manager.create_task("081")

List all worktrees

worktrees = manager.list_all

Instance Method Summary collapse

Constructor Details

#initialize(config: nil, project_root: Dir.pwd) ⇒ WorktreeManager

Initialize a new WorktreeManager

Parameters:

  • config (WorktreeConfig, nil) (defaults to: nil)

    Worktree configuration (loaded if nil)

  • project_root (String) (defaults to: Dir.pwd)

    Project root directory



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 29

def initialize(config: nil, project_root: Dir.pwd)
  @project_root = project_root

  # Initialize molecules
  @config_loader = Molecules::ConfigLoader.new(project_root)
  @config = config || load_configuration
  @worktree_creator = Molecules::WorktreeCreator.new(config: @config)
  @worktree_lister = Molecules::WorktreeLister.new
  @worktree_remover = Molecules::WorktreeRemover.new
  @task_fetcher = Molecules::TaskFetcher.new

  # Initialize orchestrator
  @task_orchestrator = Organisms::TaskWorktreeOrchestrator.new(config: @config, project_root: project_root)
end

Instance Method Details

#configurationWorktreeConfig

Get configuration

Returns:

  • (WorktreeConfig)

    Current configuration



503
504
505
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 503

def configuration
  @config
end

#create(branch_name, options = {}) ⇒ Hash

Create a traditional worktree (not task-aware)

Examples:

manager = WorktreeManager.new
result = manager.create("feature-branch", path: "/tmp/worktree")
# => { success: true, worktree_path: "/tmp/worktree", branch: "feature-branch" }

Parameters:

  • branch_name (String)

    Branch name

  • options (Hash) (defaults to: {})

    Options for creation

Returns:

  • (Hash)

    Creation result



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 54

def create(branch_name, options = {})
  # Validate inputs
  return error_result("Branch name is required") if branch_name.nil? || branch_name.empty?

  # Check if worktree already exists
  existing = @worktree_lister.find_by_branch(branch_name)
  if existing
    return error_result("Worktree already exists for branch: #{branch_name}")
  end

  # Handle dry run
  if options[:dry_run]
    return dry_run_traditional_creation(branch_name, options)
  end

  # Create the worktree
  result = @worktree_creator.create_traditional(
    branch_name,
    options[:path],
    git_root: @project_root,
    source: options[:source]
  )

  if result[:success]
    result[:message] = "Worktree created successfully"

    # Execute after-create hooks if configured
    hooks = @config.after_create_hooks
    if hooks && hooks.any?
      require_relative "../molecules/hook_executor"
      hook_executor = Molecules::HookExecutor.new
      hook_result = hook_executor.execute_hooks(
        hooks,
        worktree_path: result[:worktree_path],
        project_root: @project_root,
        task_data: nil  # No task data for traditional worktrees
      )

      if hook_result[:success]
        result[:hooks_results] = hook_result[:results]
      else
        # Hooks are non-blocking - failures become warnings
        result[:warnings] = hook_result[:errors] if hook_result[:errors]&.any?
        result[:hooks_results] = hook_result[:results]
      end
    end
  end

  result
rescue => e
  error_result("Unexpected error: #{e.message}")
end

#create_branch(branch_name, options = {}) ⇒ Hash

Create a worktree for a branch (local or remote)

Examples:

Remote branch

result = manager.create_branch("origin/feature/auth")

Local branch

result = manager.create_branch("my-feature")

Parameters:

  • branch_name (String)

    Branch name (e.g., “feature” or “origin/feature”)

  • options (Hash) (defaults to: {})

    Options for creation

Returns:

  • (Hash)

    Creation result



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 202

def create_branch(branch_name, options = {})
  return error_result("Branch name is required") if branch_name.nil? || branch_name.empty?

  # Check if worktree already exists for this branch
  # Extract just the branch name (remove remote prefix if present)
  local_branch_name = branch_name.include?("/") ? branch_name.split("/").last : branch_name
  existing = @worktree_lister.find_by_branch(local_branch_name) ||
    @worktree_lister.find_by_branch(branch_name)

  if existing && !options[:force]
    return error_result("Worktree already exists at: #{existing.path}")
  end

  # Handle dry run
  if options[:dry_run]
    return dry_run_branch_creation(branch_name, options)
  end

  # Create the worktree
  result = @worktree_creator.create_for_branch(
    branch_name,
    @config,
    git_root: @project_root
  )

  if result[:success]
    result[:message] = "Branch worktree created successfully"

    # Execute after-create hooks if configured
    hooks = @config.after_create_hooks
    if hooks && hooks.any?
      require_relative "../molecules/hook_executor"
      hook_executor = Molecules::HookExecutor.new
      hook_result = hook_executor.execute_hooks(
        hooks,
        worktree_path: result[:worktree_path],
        project_root: @project_root,
        task_data: nil
      )

      if hook_result[:success]
        result[:hooks_results] = hook_result[:results]
      else
        result[:warnings] = hook_result[:errors] if hook_result[:errors]&.any?
        result[:hooks_results] = hook_result[:results]
      end
    end
  end

  result
rescue => e
  error_result("Unexpected error: #{e.message}")
end

#create_pr(pr_number, pr_data, options = {}) ⇒ Hash

Create a worktree for a Pull Request

Examples:

manager = WorktreeManager.new
pr_data = { number: 26, title: "Add feature", head_branch: "feature/auth" }
result = manager.create_pr(26, pr_data)

Parameters:

  • pr_number (Integer)

    PR number

  • pr_data (Hash)

    PR data from Ace::Git::Molecules::PrMetadataFetcher

  • options (Hash) (defaults to: {})

    Options for creation

Returns:

  • (Hash)

    Creation result



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 135

def create_pr(pr_number, pr_data, options = {})
  return error_result("PR number is required") if pr_number.nil?
  return error_result("PR data is required") if pr_data.nil?

  # Check if worktree already exists for this PR's branch
  head_branch = pr_data[:head_branch]
  existing = @worktree_lister.find_by_branch("pr-#{pr_number}") ||
    @worktree_lister.find_by_branch(head_branch)

  if existing && !options[:force]
    return error_result("Worktree already exists at: #{existing.path}")
  end

  # Handle dry run
  if options[:dry_run]
    return dry_run_pr_creation(pr_number, pr_data, options)
  end

  # Create the worktree
  result = @worktree_creator.create_for_pr(
    pr_data,
    @config,
    git_root: @project_root
  )

  if result[:success]
    result[:pr_number] = pr_number
    result[:pr_title] = pr_data[:title]
    result[:message] = "PR worktree created successfully"

    # Execute after-create hooks if configured
    hooks = @config.after_create_hooks
    if hooks && hooks.any?
      require_relative "../molecules/hook_executor"
      hook_executor = Molecules::HookExecutor.new
      hook_result = hook_executor.execute_hooks(
        hooks,
        worktree_path: result[:worktree_path],
        project_root: @project_root,
        task_data: pr_data
      )

      if hook_result[:success]
        result[:hooks_results] = hook_result[:results]
      else
        result[:warnings] = hook_result[:errors] if hook_result[:errors]&.any?
        result[:hooks_results] = hook_result[:results]
      end
    end
  end

  result
rescue => e
  error_result("Unexpected error: #{e.message}")
end

#create_task(task_ref, options = {}) ⇒ Hash

Create a task-aware worktree

Examples:

result = manager.create_task("081")
result = manager.create_task("081", dry_run: true)

Parameters:

  • task_ref (String)

    Task reference

  • options (Hash) (defaults to: {})

    Options for creation

Returns:

  • (Hash)

    Creation result



116
117
118
119
120
121
122
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 116

def create_task(task_ref, options = {})
  if options[:dry_run]
    @task_orchestrator.dry_run_create(task_ref, options)
  else
    @task_orchestrator.create_for_task(task_ref, options)
  end
end

#get_statusHash

Get worktree status and statistics

Examples:

status = manager.get_status
puts "Total worktrees: #{status[:statistics][:total]}"

Returns:

  • (Hash)

    Status information



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 431

def get_status
  # Get all worktrees with task associations
  worktrees = @worktree_lister.list_with_tasks
  stats = @worktree_lister.get_statistics

  # Get task worktree status
  task_status = @task_orchestrator.get_task_worktree_status

  result = {
    success: true,
    worktrees: worktrees,
    statistics: stats,
    configuration: @config.to_h
  }
  result[:task_status] = task_status[:status] if task_status[:success]
  result
rescue => e
  error_result("Failed to get worktree status: #{e.message}")
end

#list_all(options = {}) ⇒ Hash

List all worktrees

Examples:

result = manager.list_all
result = manager.list_all(format: :json, show_tasks: true)

Parameters:

  • options (Hash) (defaults to: {})

    Listing options

Options Hash (options):

  • :format (Symbol)

    Output format (:table, :json, :simple)

  • :show_tasks (Boolean)

    Include task associations

  • :task_associated (Boolean)

    Filter by task association

  • :usable (Boolean)

    Filter by usability

  • :search (String)

    Filter by search pattern

Returns:

  • (Hash)

    Listing result



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 270

def list_all(options = {})
  task_filter_requested = !options[:task_associated].nil?

  # Get worktrees
  worktrees = if options[:show_tasks] || task_filter_requested
    @worktree_lister.list_with_tasks
  else
    @worktree_lister.list_all
  end

  # Apply filters
  if !options[:task_associated].nil? || !options[:usable].nil? || options[:search]
    worktrees = @worktree_lister.filter(
      worktrees,
      task_associated: options[:task_associated],
      usable: options[:usable],
      branch_pattern: options[:search]
    )
  end

  # Format output
  formatted_output = @worktree_lister.format_for_display(
    worktrees,
    options[:format] || :table
  )

  # Get statistics
  stats = @worktree_lister.get_statistics(worktrees)

  {
    success: true,
    worktrees: worktrees,
    formatted_output: formatted_output,
    statistics: stats,
    count: worktrees.length
  }
rescue => e
  error_result("Failed to list worktrees: #{e.message}")
end

#pruneHash

Prune deleted worktrees

Examples:

result = manager.prune
# => { success: true, message: "Pruned 2 worktrees", pruned_count: 2 }

Returns:

  • (Hash)

    Prune result



416
417
418
419
420
421
422
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 416

def prune
  result = @worktree_remover.prune
  result[:message] = "Worktree pruning completed successfully" if result[:success]
  result
rescue => e
  error_result("Failed to prune worktrees: #{e.message}")
end

#reload_configurationWorktreeConfig

Reload configuration

Returns:

  • (WorktreeConfig)

    Reloaded configuration



510
511
512
513
514
515
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 510

def reload_configuration
  @config = load_configuration
  @config_loader.reset_cache!
  @task_orchestrator = Organisms::TaskWorktreeOrchestrator.new(config: @config, project_root: @project_root)
  @config
end

#remove(identifier, options = {}) ⇒ Hash

Remove a worktree

Examples:

result = manager.remove("081")           # By task ID
result = manager.remove("feature-branch") # By branch name
result = manager.remove("/path/to/worktree", force: true)

Parameters:

  • identifier (String)

    Worktree identifier

  • options (Hash) (defaults to: {})

    Removal options

Options Hash (options):

  • :force (Boolean)

    Force removal even with changes

  • :remove_directory (Boolean)

    Also remove the directory

  • :ignore_untracked (Boolean)

    Ignore untracked files when checking changes

Returns:

  • (Hash)

    Removal result



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 363

def remove(identifier, options = {})
  return error_result("Worktree identifier is required") if identifier.nil? || identifier.empty?

  # Find the worktree
  worktree = find_worktree_by_identifier(identifier)

  unless worktree
    # Worktree not found - check if we should try branch-only deletion
    if options[:delete_branch]
      result = attempt_branch_only_deletion(identifier, options[:force])
      return result if result[:success]
    end

    return error_result("Worktree not found: #{identifier}")
  end

  # Remove the worktree
  result = @worktree_remover.remove(
    worktree.path,
    force: options[:force],
    remove_directory: options[:remove_directory] != false,
    delete_branch: options[:delete_branch] == true,
    ignore_untracked: options[:ignore_untracked] == true
  )

  if result[:success]
    result[:message] = "Worktree removed successfully: #{worktree.description}"
  end

  result
rescue => e
  error_result("Unexpected error: #{e.message}")
end

#remove_task(task_ref, options = {}) ⇒ Hash

Remove a task worktree with full cleanup

Examples:

result = manager.remove_task("081", force: true)

Parameters:

  • task_ref (String)

    Task reference

  • options (Hash) (defaults to: {})

    Removal options

Returns:

  • (Hash)

    Removal result



405
406
407
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 405

def remove_task(task_ref, options = {})
  @task_orchestrator.remove_task_worktree(task_ref, options)
end

#search(query, options = {}) ⇒ Hash

Search for worktrees

Examples:

result = manager.search("auth", search_in: [:branch, :task_id])

Parameters:

  • query (String)

    Search query

  • options (Hash) (defaults to: {})

    Search options

Returns:

  • (Hash)

    Search result



459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 459

def search(query, options = {})
  return error_result("Search query is required") if query.nil? || query.empty?

  search_in = options[:search_in] || [:branch, :path, :task_id]
  worktrees = @worktree_lister.search(query, search_in: search_in)

  {
    success: true,
    query: query,
    search_in: search_in,
    results: worktrees,
    count: worktrees.length
  }
rescue => e
  error_result("Search failed: #{e.message}")
end

#switch(identifier) ⇒ Hash

Switch to a worktree

Examples:

result = manager.switch("081")        # By task ID
result = manager.switch("feature-branch")  # By branch name
result = manager.switch("task.081")  # By directory name
result = manager.switch("/path/to/worktree")  # By path

Parameters:

  • identifier (String)

    Worktree identifier (task ID, branch name, directory, or path)

Returns:

  • (Hash)

    Switch result



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 320

def switch(identifier)
  return error_result("Worktree identifier is required") if identifier.nil? || identifier.empty?

  # Try different ways to find the worktree
  worktree = find_worktree_by_identifier(identifier)
  return error_result("Worktree not found: #{identifier}") unless worktree

  # Check if worktree exists and is usable
  unless worktree.exists?
    return error_result("Worktree directory does not exist: #{worktree.path}")
  end

  unless worktree.usable?
    return error_result("Worktree is not usable: #{worktree.description}")
  end

  # Return the path for the caller to use
  {
    success: true,
    message: "Found worktree: #{worktree.description}",
    worktree_path: worktree.path,
    branch: worktree.branch,
    task_id: worktree.task_id,
    description: worktree.description
  }
rescue => e
  error_result("Unexpected error: #{e.message}")
end

#validate_configurationHash

Validate worktree configuration

Examples:

validation = manager.validate_configuration
if validation[:valid]
  puts "Configuration is valid"
else
  puts "Errors: #{validation[:errors].join(', ')}"
end

Returns:

  • (Hash)

    Validation result



487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/ace/git/worktree/organisms/worktree_manager.rb', line 487

def validate_configuration
  errors = @config.validate

  {
    success: errors.empty?,
    valid: errors.empty?,
    errors: errors,
    configuration: @config.to_h
  }
rescue => e
  error_result("Configuration validation failed: #{e.message}")
end