Class: Worktrees::WorktreeManager

Inherits:
Object
  • Object
show all
Defined in:
lib/worktrees/worktree_manager.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(repository = nil, config = nil) ⇒ WorktreeManager

Returns a new instance of WorktreeManager.



9
10
11
12
13
14
15
16
17
18
# File 'lib/worktrees/worktree_manager.rb', line 9

def initialize(repository = nil, config = nil)
  begin
    repo_root = find_git_repository_root
    @repository = repository || Models::Repository.new(repo_root)
  rescue GitError => e
    # Re-raise with more context if we're not in a git repo
    raise GitError, "Not in a git repository. #{e.message}"
  end
  @config = config || Models::WorktreeConfig.load
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



7
8
9
# File 'lib/worktrees/worktree_manager.rb', line 7

def config
  @config
end

#repositoryObject (readonly)

Returns the value of attribute repository.



7
8
9
# File 'lib/worktrees/worktree_manager.rb', line 7

def repository
  @repository
end

Instance Method Details

#create_worktree(name, base_ref = nil, options = {}) ⇒ Object

Raises:



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/worktrees/worktree_manager.rb', line 20

def create_worktree(name, base_ref = nil, options = {})
  # Validate arguments
  raise ValidationError, 'Name is required' if name.nil?
  raise ValidationError, 'Name cannot be empty' if name.empty?

  # Use FeatureWorktree validation for better error messages
  unless Models::FeatureWorktree.validate_name(name)
    # Check what specific error to show
    unless name.match?(Models::FeatureWorktree::NAME_PATTERN)
      raise ValidationError, "Invalid name format '#{name}'. Names must match pattern: NNN-kebab-feature"
    end
    feature_part = name.split('-', 2)[1]
    if feature_part && Models::FeatureWorktree::RESERVED_NAMES.include?(feature_part.downcase)
      raise ValidationError, "Reserved name '#{feature_part}' not allowed in worktree names"
    end
  end

  # Use default base if none provided
  base_ref ||= @repository.default_branch

  # Check if base reference exists
  unless @repository.branch_exists?(base_ref)
    raise GitError, "Base reference '#{base_ref}' not found"
  end

  # Check for duplicate worktree
  existing = find_worktree(name)
  if existing
    raise ValidationError, "Worktree '#{name}' already exists"
  end

  # Create worktree path
  worktree_path = File.join(@config.expand_worktrees_root, name)

  # Create worktrees root directory if it doesn't exist
  FileUtils.mkdir_p(@config.expand_worktrees_root)

  # Create the worktree
  unless GitOperations.create_worktree(worktree_path, name, base_ref)
    raise GitError, "Failed to create worktree '#{name}'"
  end

  # Verify worktree was created
  unless File.directory?(worktree_path)
    raise FileSystemError, "Worktree directory was not created: #{worktree_path}"
  end

  # Return the created worktree
  Models::FeatureWorktree.new(
    name: name,
    path: worktree_path,
    branch: name,
    base_ref: base_ref,
    status: :clean,
    created_at: Time.now,
    repository_path: @repository.root_path
  )
end

#current_worktreeObject



168
169
170
171
172
173
174
175
# File 'lib/worktrees/worktree_manager.rb', line 168

def current_worktree
  current_path = Dir.pwd
  worktrees = list_worktrees

  worktrees.find do |worktree|
    current_path.start_with?(worktree.path)
  end
end

#find_worktree(name) ⇒ Object



112
113
114
115
# File 'lib/worktrees/worktree_manager.rb', line 112

def find_worktree(name)
  worktrees = list_worktrees
  worktrees.find { |wt| wt.name == name }
end

#list_worktrees(format: :objects, status: nil) ⇒ Object



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
106
107
108
109
110
# File 'lib/worktrees/worktree_manager.rb', line 79

def list_worktrees(format: :objects, status: nil)
  git_worktrees = GitOperations.list_worktrees
  worktrees = []

  git_worktrees.each do |git_wt|
    next unless git_wt[:path] && File.directory?(git_wt[:path])

    # Extract name from path
    name = File.basename(git_wt[:path])
    next unless @config.valid_name?(name)

    # Determine status
    wt_status = determine_status(git_wt[:path])

    # Skip if status filter doesn't match
    next if status && wt_status != status

    worktree = Models::FeatureWorktree.new(
      name: name,
      path: git_wt[:path],
      branch: git_wt[:branch] || name,
      base_ref: detect_base_ref(git_wt[:branch] || name),
      status: wt_status,
      created_at: File.ctime(git_wt[:path]),
      repository_path: @repository.root_path
    )

    worktrees << worktree
  end

  worktrees.sort_by(&:name)
end

#remove_worktree(name, options = {}) ⇒ Object

Raises:



132
133
134
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
# File 'lib/worktrees/worktree_manager.rb', line 132

def remove_worktree(name, options = {})
  worktree = find_worktree(name)
  raise NotFoundError, "Worktree '#{name}' not found" unless worktree

  # Safety checks
  if worktree.active?
    raise StateError, "Cannot remove active worktree '#{name}'. Switch to a different worktree first"
  end

  if worktree.dirty? && !options[:force_untracked]
    raise StateError, "Worktree '#{name}' has uncommitted changes. Commit or stash changes, or use --force-untracked for untracked files only"
  end

  # Check for unpushed commits
  if GitOperations.has_unpushed_commits?(worktree.branch)
    raise StateError, "Worktree '#{name}' has unpushed commits. Push commits first or use --force"
  end

  # Remove the worktree
  force = options[:force_untracked] || options[:force]
  unless GitOperations.remove_worktree(worktree.path, force: force)
    raise GitError, "Failed to remove worktree '#{name}'"
  end

  # Optionally delete branch
  if options[:delete_branch]
    if GitOperations.is_merged?(worktree.branch)
      GitOperations.delete_branch(worktree.branch)
    else
      raise StateError, "Branch '#{worktree.branch}' is not fully merged. Use merge-base check or --force"
    end
  end

  true
end

#switch_to_worktree(name) ⇒ Object

Raises:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/worktrees/worktree_manager.rb', line 117

def switch_to_worktree(name)
  worktree = find_worktree(name)
  raise ValidationError, "Worktree '#{name}' not found" unless worktree

  # Check current state for warnings
  current = current_worktree
  if current && current.dirty?
    warn "Warning: Previous worktree '#{current.name}' has uncommitted changes"
  end

  # Change to worktree directory
  Dir.chdir(worktree.path)
  worktree
end