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,416 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Git
|
|
5
|
+
module Worktree
|
|
6
|
+
module Molecules
|
|
7
|
+
# Worktree remover molecule
|
|
8
|
+
#
|
|
9
|
+
# Removes git worktrees with proper cleanup, validation, and error handling.
|
|
10
|
+
# Provides options for force removal and handles various edge cases.
|
|
11
|
+
#
|
|
12
|
+
# @example Remove a worktree
|
|
13
|
+
# remover = WorktreeRemover.new
|
|
14
|
+
# success = remover.remove("/path/to/worktree")
|
|
15
|
+
#
|
|
16
|
+
# @example Force remove with changes
|
|
17
|
+
# success = remover.remove("/path/to/worktree", force: true)
|
|
18
|
+
class WorktreeRemover
|
|
19
|
+
# Fallback timeout for git commands
|
|
20
|
+
# Used only when config is unavailable
|
|
21
|
+
FALLBACK_TIMEOUT = 30
|
|
22
|
+
|
|
23
|
+
# Initialize a new WorktreeRemover
|
|
24
|
+
#
|
|
25
|
+
# @param timeout [Integer, nil] Command timeout in seconds (uses config default if nil)
|
|
26
|
+
def initialize(timeout: nil)
|
|
27
|
+
@timeout = timeout || config_timeout
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Get timeout from config or fallback
|
|
33
|
+
# @return [Integer] Timeout in seconds
|
|
34
|
+
def config_timeout
|
|
35
|
+
Ace::Git::Worktree.remove_timeout
|
|
36
|
+
rescue
|
|
37
|
+
FALLBACK_TIMEOUT
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
public
|
|
41
|
+
|
|
42
|
+
# Remove a worktree by path
|
|
43
|
+
#
|
|
44
|
+
# @param worktree_path [String] Path to the worktree directory
|
|
45
|
+
# @param force [Boolean] Force removal even if there are uncommitted changes
|
|
46
|
+
# @param remove_directory [Boolean] Also remove the worktree directory
|
|
47
|
+
# @param delete_branch [Boolean] Also delete the associated branch
|
|
48
|
+
# @param ignore_untracked [Boolean] Treat untracked files as clean for removal checks
|
|
49
|
+
# @return [Hash] Result with :success, :message, :error
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# remover = WorktreeRemover.new
|
|
53
|
+
# result = remover.remove("/project/.ace-wt/task.081")
|
|
54
|
+
# # => { success: true, message: "Worktree removed successfully", error: nil }
|
|
55
|
+
def remove(
|
|
56
|
+
worktree_path,
|
|
57
|
+
force: false,
|
|
58
|
+
remove_directory: true,
|
|
59
|
+
delete_branch: false,
|
|
60
|
+
ignore_untracked: false
|
|
61
|
+
)
|
|
62
|
+
return error_result("Worktree path is required") if worktree_path.nil? || worktree_path.empty?
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
expanded_path = File.expand_path(worktree_path)
|
|
66
|
+
|
|
67
|
+
# Check if worktree exists
|
|
68
|
+
worktree_info = find_worktree_info(expanded_path)
|
|
69
|
+
return error_result("Worktree not found at #{expanded_path}") unless worktree_info
|
|
70
|
+
|
|
71
|
+
# Check for uncommitted changes
|
|
72
|
+
if !force && has_uncommitted_changes?(expanded_path, ignore_untracked: ignore_untracked)
|
|
73
|
+
return error_result("Worktree has uncommitted changes. Use --force to remove anyway.")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Store branch name before removal
|
|
77
|
+
branch_name = worktree_info.branch
|
|
78
|
+
|
|
79
|
+
# Remove the worktree using git
|
|
80
|
+
# When ignore_untracked is true, we've already verified there are no tracked changes,
|
|
81
|
+
# so pass force: true to skip git's own untracked-file check.
|
|
82
|
+
result = remove_git_worktree(expanded_path, force: force || ignore_untracked)
|
|
83
|
+
return result unless result[:success]
|
|
84
|
+
|
|
85
|
+
# Optionally remove the directory
|
|
86
|
+
if remove_directory && File.exist?(expanded_path)
|
|
87
|
+
remove_worktree_directory(expanded_path)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Optionally delete the branch
|
|
91
|
+
branch_deleted = false
|
|
92
|
+
if delete_branch && branch_name && !branch_name.empty?
|
|
93
|
+
delete_result = delete_branch_if_safe(branch_name, force)
|
|
94
|
+
branch_deleted = delete_result[:success]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
success: true,
|
|
99
|
+
message: "Worktree removed successfully",
|
|
100
|
+
path: expanded_path,
|
|
101
|
+
branch: branch_name,
|
|
102
|
+
branch_deleted: branch_deleted,
|
|
103
|
+
error: nil
|
|
104
|
+
}
|
|
105
|
+
rescue => e
|
|
106
|
+
error_result("Unexpected error: #{e.message}")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Remove a worktree by branch name
|
|
111
|
+
#
|
|
112
|
+
# @param branch_name [String] Branch name of the worktree
|
|
113
|
+
# @param force [Boolean] Force removal even if there are uncommitted changes
|
|
114
|
+
# @return [Hash] Result with :success, :message, :error
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# result = remover.remove_by_branch("081-fix-auth")
|
|
118
|
+
def remove_by_branch(branch_name, force: false)
|
|
119
|
+
return error_result("Branch name is required") if branch_name.nil? || branch_name.empty?
|
|
120
|
+
|
|
121
|
+
# Find worktree by branch
|
|
122
|
+
worktree_info = find_worktree_by_branch(branch_name)
|
|
123
|
+
return error_result("No worktree found for branch: #{branch_name}") unless worktree_info
|
|
124
|
+
|
|
125
|
+
remove(worktree_info.path, force: force)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Remove a worktree by task ID
|
|
129
|
+
#
|
|
130
|
+
# @param task_id [String] Task ID
|
|
131
|
+
# @param force [Boolean] Force removal even if there are uncommitted changes
|
|
132
|
+
# @return [Hash] Result with :success, :message, :error
|
|
133
|
+
#
|
|
134
|
+
# @example
|
|
135
|
+
# result = remover.remove_by_task_id("081")
|
|
136
|
+
def remove_by_task_id(task_id, force: false)
|
|
137
|
+
return error_result("Task ID is required") if task_id.nil? || task_id.empty?
|
|
138
|
+
|
|
139
|
+
# Find worktree by task ID
|
|
140
|
+
worktree_info = find_worktree_by_task_id(task_id)
|
|
141
|
+
return error_result("No worktree found for task: #{task_id}") unless worktree_info
|
|
142
|
+
|
|
143
|
+
remove(worktree_info.path, force: force)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Remove multiple worktrees
|
|
147
|
+
#
|
|
148
|
+
# @param worktree_paths [Array<String>] Array of worktree paths
|
|
149
|
+
# @param force [Boolean] Force removal even if there are uncommitted changes
|
|
150
|
+
# @return [Hash] Result with :success, :removed, :failed, :errors
|
|
151
|
+
#
|
|
152
|
+
# @example
|
|
153
|
+
# result = remover.remove_multiple(["/path1", "/path2"], force: true)
|
|
154
|
+
# # => { success: true, removed: ["/path1"], failed: ["/path2"], errors: {...} }
|
|
155
|
+
def remove_multiple(worktree_paths, force: false)
|
|
156
|
+
return error_result("Worktree paths array is required") if worktree_paths.nil? || worktree_paths.empty?
|
|
157
|
+
|
|
158
|
+
results = {
|
|
159
|
+
success: true,
|
|
160
|
+
removed: [],
|
|
161
|
+
failed: [],
|
|
162
|
+
errors: {}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
Array(worktree_paths).each do |path|
|
|
166
|
+
result = remove(path, force: force)
|
|
167
|
+
if result[:success]
|
|
168
|
+
results[:removed] << path
|
|
169
|
+
else
|
|
170
|
+
results[:success] = false
|
|
171
|
+
results[:failed] << path
|
|
172
|
+
results[:errors][path] = result[:error]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
results
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Prune deleted worktrees (cleanup git metadata)
|
|
180
|
+
#
|
|
181
|
+
# @return [Hash] Result with :success, :message, :pruned_count
|
|
182
|
+
#
|
|
183
|
+
# @example
|
|
184
|
+
# result = remover.prune
|
|
185
|
+
# # => { success: true, message: "Pruned 2 worktrees", pruned_count: 2 }
|
|
186
|
+
def prune
|
|
187
|
+
result = execute_git_worktree_prune
|
|
188
|
+
if result[:success]
|
|
189
|
+
# Parse output to count pruned worktrees
|
|
190
|
+
pruned_count = parse_prune_output(result[:output])
|
|
191
|
+
|
|
192
|
+
{
|
|
193
|
+
success: true,
|
|
194
|
+
message: "Pruned #{pruned_count} worktree(s)",
|
|
195
|
+
pruned_count: pruned_count,
|
|
196
|
+
error: nil
|
|
197
|
+
}
|
|
198
|
+
else
|
|
199
|
+
error_result("Failed to prune worktrees: #{result[:error]}")
|
|
200
|
+
end
|
|
201
|
+
rescue => e
|
|
202
|
+
error_result("Unexpected error during prune: #{e.message}")
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Check if a worktree can be safely removed
|
|
206
|
+
#
|
|
207
|
+
# @param worktree_path [String] Path to the worktree
|
|
208
|
+
# @return [Hash] Safety check result with :safe, :warnings, :errors
|
|
209
|
+
#
|
|
210
|
+
# @example
|
|
211
|
+
# check = remover.check_removal_safety("/path/to/worktree")
|
|
212
|
+
# if check[:safe]
|
|
213
|
+
# remover.remove("/path/to/worktree")
|
|
214
|
+
# else
|
|
215
|
+
# puts "Cannot remove: #{check[:errors].join(', ')}"
|
|
216
|
+
# end
|
|
217
|
+
def check_removal_safety(worktree_path)
|
|
218
|
+
expanded_path = File.expand_path(worktree_path)
|
|
219
|
+
|
|
220
|
+
result = {
|
|
221
|
+
safe: true,
|
|
222
|
+
warnings: [],
|
|
223
|
+
errors: []
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# Check if worktree exists
|
|
227
|
+
worktree_info = find_worktree_info(expanded_path)
|
|
228
|
+
unless worktree_info
|
|
229
|
+
result[:safe] = false
|
|
230
|
+
result[:errors] << "Worktree not found"
|
|
231
|
+
return result
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Check for uncommitted changes
|
|
235
|
+
if has_uncommitted_changes?(expanded_path)
|
|
236
|
+
result[:safe] = false
|
|
237
|
+
result[:errors] << "Worktree has uncommitted changes"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Check if it's the current worktree
|
|
241
|
+
current_dir = Dir.pwd
|
|
242
|
+
if File.expand_path(current_dir) == expanded_path
|
|
243
|
+
result[:warnings] << "Currently in this worktree"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Check if it's the main worktree
|
|
247
|
+
if worktree_info.branch.nil || worktree_info.detached || worktree_info.bare
|
|
248
|
+
result[:warnings] << "This might be the main worktree"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
result
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
# Find worktree info by path
|
|
257
|
+
#
|
|
258
|
+
# @param path [String] Worktree path
|
|
259
|
+
# @return [WorktreeInfo, nil] Worktree info or nil
|
|
260
|
+
def find_worktree_info(path)
|
|
261
|
+
require_relative "worktree_lister"
|
|
262
|
+
lister = WorktreeLister.new
|
|
263
|
+
lister.find_by_path(path)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Find worktree info by branch name
|
|
267
|
+
#
|
|
268
|
+
# @param branch_name [String] Branch name
|
|
269
|
+
# @return [WorktreeInfo, nil] Worktree info or nil
|
|
270
|
+
def find_worktree_by_branch(branch_name)
|
|
271
|
+
require_relative "worktree_lister"
|
|
272
|
+
lister = WorktreeLister.new
|
|
273
|
+
lister.find_by_branch(branch_name)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Find worktree info by task ID
|
|
277
|
+
#
|
|
278
|
+
# @param task_id [String] Task ID
|
|
279
|
+
# @return [WorktreeInfo, nil] Worktree info or nil
|
|
280
|
+
def find_worktree_by_task_id(task_id)
|
|
281
|
+
require_relative "worktree_lister"
|
|
282
|
+
lister = WorktreeLister.new
|
|
283
|
+
lister.find_by_task_id(task_id)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Remove worktree using git command
|
|
287
|
+
#
|
|
288
|
+
# @param worktree_path [String] Worktree path
|
|
289
|
+
# @return [Hash] Command result
|
|
290
|
+
def remove_git_worktree(worktree_path, force: false)
|
|
291
|
+
require_relative "../atoms/git_command"
|
|
292
|
+
args = ["remove"]
|
|
293
|
+
args << "--force" if force
|
|
294
|
+
args << worktree_path
|
|
295
|
+
result = Atoms::GitCommand.worktree(*args, timeout: @timeout)
|
|
296
|
+
|
|
297
|
+
if result[:success]
|
|
298
|
+
{success: true, message: "Git worktree removed successfully"}
|
|
299
|
+
else
|
|
300
|
+
error_result("Failed to remove git worktree: #{result[:error]}")
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Remove the worktree directory
|
|
305
|
+
#
|
|
306
|
+
# @param worktree_path [String] Worktree path
|
|
307
|
+
def remove_worktree_directory(worktree_path)
|
|
308
|
+
if File.exist?(worktree_path)
|
|
309
|
+
FileUtils.rm_rf(worktree_path)
|
|
310
|
+
end
|
|
311
|
+
rescue => e
|
|
312
|
+
warn "Warning: Failed to remove worktree directory: #{e.message}"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Check if worktree has uncommitted changes
|
|
316
|
+
#
|
|
317
|
+
# @param worktree_path [String] Worktree path
|
|
318
|
+
# @param ignore_untracked [Boolean] Ignore untracked files when checking cleanliness
|
|
319
|
+
# @return [Boolean] true if there are uncommitted changes
|
|
320
|
+
def has_uncommitted_changes?(worktree_path, ignore_untracked: false)
|
|
321
|
+
return false unless File.exist?(worktree_path)
|
|
322
|
+
|
|
323
|
+
# Change to worktree directory and check git status
|
|
324
|
+
original_dir = Dir.pwd
|
|
325
|
+
begin
|
|
326
|
+
Dir.chdir(worktree_path)
|
|
327
|
+
status_args = ["status", "--porcelain"]
|
|
328
|
+
status_args << "--untracked-files=no" if ignore_untracked
|
|
329
|
+
|
|
330
|
+
result = execute_git_command(*status_args)
|
|
331
|
+
result[:success] && !result[:output].strip.empty?
|
|
332
|
+
ensure
|
|
333
|
+
Dir.chdir(original_dir)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Execute git worktree prune command
|
|
338
|
+
#
|
|
339
|
+
# @return [Hash] Command result
|
|
340
|
+
def execute_git_worktree_prune
|
|
341
|
+
require_relative "../atoms/git_command"
|
|
342
|
+
Atoms::GitCommand.worktree("prune", timeout: @timeout)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Parse prune output to count pruned worktrees
|
|
346
|
+
#
|
|
347
|
+
# @param output [String] Git prune command output
|
|
348
|
+
# @return [Integer] Number of pruned worktrees
|
|
349
|
+
def parse_prune_output(output)
|
|
350
|
+
return 0 if output.nil? || output.empty?
|
|
351
|
+
|
|
352
|
+
# Look for lines like "Pruning worktree /path/to/worktree"
|
|
353
|
+
lines = output.split("\n")
|
|
354
|
+
lines.count { |line| line.include?("Pruning worktree") }
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Execute git command
|
|
358
|
+
#
|
|
359
|
+
# @param args [Array<String>] Command arguments
|
|
360
|
+
# @return [Hash] Command result
|
|
361
|
+
def execute_git_command(*args)
|
|
362
|
+
require_relative "../atoms/git_command"
|
|
363
|
+
Atoms::GitCommand.execute(*args, timeout: @timeout)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Create an error result hash
|
|
367
|
+
#
|
|
368
|
+
# @param message [String] Error message
|
|
369
|
+
# @return [Hash] Error result hash
|
|
370
|
+
def error_result(message)
|
|
371
|
+
{
|
|
372
|
+
success: false,
|
|
373
|
+
message: nil,
|
|
374
|
+
error: message
|
|
375
|
+
}
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
public
|
|
379
|
+
|
|
380
|
+
# Delete a branch if it's safe to do so
|
|
381
|
+
#
|
|
382
|
+
# @param branch_name [String] Branch name to delete
|
|
383
|
+
# @param force [Boolean] Force deletion even if not merged
|
|
384
|
+
# @return [Hash] Result with :success, :message, :error
|
|
385
|
+
def delete_branch_if_safe(branch_name, force)
|
|
386
|
+
require_relative "../atoms/git_command"
|
|
387
|
+
|
|
388
|
+
# Check if branch is already merged (unless forcing)
|
|
389
|
+
unless force
|
|
390
|
+
# Check if branch is merged into current branch
|
|
391
|
+
result = Atoms::GitCommand.execute("branch", "--merged", timeout: @timeout)
|
|
392
|
+
if result[:success]
|
|
393
|
+
merged_branches = result[:output].split("\n").map(&:strip).map { |b| b.gsub(/^\*?\s*/, "") }
|
|
394
|
+
unless merged_branches.include?(branch_name)
|
|
395
|
+
# Branch is not merged, don't delete unless forced
|
|
396
|
+
warn "Warning: Branch #{branch_name} is not merged. Skipping deletion. Use --force to delete anyway."
|
|
397
|
+
return {success: false, message: "Branch not merged", error: nil}
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Delete the branch
|
|
403
|
+
delete_flag = force ? "-D" : "-d"
|
|
404
|
+
result = Atoms::GitCommand.execute("branch", delete_flag, branch_name, timeout: @timeout)
|
|
405
|
+
|
|
406
|
+
if result[:success]
|
|
407
|
+
{success: true, message: "Branch #{branch_name} deleted", error: nil}
|
|
408
|
+
else
|
|
409
|
+
{success: false, message: nil, error: "Failed to delete branch: #{result[:error]}"}
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|