carson 2.31.0 → 2.32.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96820bedb9187d90787072b441c35129457a7d45202899ea520a9bb1e3b50876
4
- data.tar.gz: f8963204bde45cee5e7e662dcc5f5bb947e84372f0d24a03d6ef494c3653adbf
3
+ metadata.gz: 99666417783ceb949cdef39bbf1595465bf0a5f674607229b4b937a4ae8b6197
4
+ data.tar.gz: a8e55f943e2ab34a26d50b0468e10cecbdbf5879eaeef1a7323e99e80715dac3
5
5
  SHA512:
6
- metadata.gz: 616dbf56c4c09e6327767ccaaa8b88595a51ae4e8275290d1b6f1349fb6b2e25cb3789e4891699b3e6b8ff878d209bf4503adcc4974506b1a72ddcee374e432d
7
- data.tar.gz: 83b8827df9139e9a81fa587a00816333d37fc87d03a3f7f0388bbd3fb8722138978b7b99844ccbfa6713243a651c4886774f88400c226c060398f1824b64e7ba
6
+ metadata.gz: 2342dcf7bde01d3632741e76adaefe6c499a03a36d11bda476870419e43f87caa51ae9837fc5f1cdbbc9b5c083b4b2058824fc5c0f346788ca7f2658aecbe2ab
7
+ data.tar.gz: f0f8fe556fa65044b3789a45cd25d1fc354e2865a5b923843bf3249a30259bac989eb74f0cb5bb1ff2072c4dc548911ca5f5d26d13da933baceb70161a527993
data/RELEASE.md CHANGED
@@ -5,6 +5,17 @@ Release-note scope rule:
5
5
  - `RELEASE.md` records only version deltas, breaking changes, and migration actions.
6
6
  - Operational usage guides live in `MANUAL.md` and `API.md`.
7
7
 
8
+ ## 2.32.0 — Worktree-Aware Pruning
9
+
10
+ ### What changed
11
+
12
+ - **Prune now clears worktrees that block branch deletion.** When a branch is checked out in a worktree, `carson prune` safely removes the worktree first (refuses if uncommitted changes exist), then deletes the branch. Previously, these branches were silently skipped.
13
+ - **Honest skip reporting.** Non-verbose output no longer says "No stale branches" when branches were detected but couldn't be pruned. Shows "Skipped N branch(es)" or "Pruned N, skipped M" as appropriate, with a `--verbose` hint.
14
+
15
+ ### Migration
16
+
17
+ - No action required. Existing prune behaviour is strictly improved.
18
+
8
19
  ## 2.31.0 — Worktree Lifecycle + Prune --all
9
20
 
10
21
  ### What changed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.31.0
1
+ 2.32.0
@@ -25,11 +25,15 @@ module Carson
25
25
  puts_verbose "prune_summary: deleted=#{counters.fetch( :deleted )} skipped=#{counters.fetch( :skipped )}"
26
26
  unless verbose?
27
27
  deleted_count = counters.fetch( :deleted )
28
- if deleted_count.zero?
29
- puts_line "No stale branches."
28
+ skipped_count = counters.fetch( :skipped )
29
+ message = if deleted_count > 0 && skipped_count > 0
30
+ "Pruned #{deleted_count}, skipped #{skipped_count} (--verbose for details)."
31
+ elsif deleted_count > 0
32
+ "Pruned #{deleted_count} stale branch#{plural_suffix( count: deleted_count )}."
30
33
  else
31
- puts_line "Pruned #{deleted_count} stale branch#{plural_suffix( count: deleted_count )}."
34
+ "Skipped #{skipped_count} branch#{plural_suffix( count: skipped_count )} (--verbose for details)."
32
35
  end
36
+ puts_line message
33
37
  end
34
38
  EXIT_OK
35
39
  end
@@ -93,7 +97,7 @@ module Carson
93
97
  )
94
98
  return prune_force_delete_skipped( branch: branch, upstream: upstream, delete_error_text: delete_error_text, force_error: force_error ) if merged_pr.nil?
95
99
 
96
- force_stdout, force_stderr, force_success, = git_run( "branch", "-D", branch )
100
+ force_stdout, force_stderr, force_success = force_delete_local_branch( branch: branch )
97
101
  return prune_force_delete_success( branch: branch, upstream: upstream, merged_pr: merged_pr, force_stdout: force_stdout ) if force_success
98
102
 
99
103
  prune_force_delete_failed( branch: branch, upstream: upstream, force_stderr: force_stderr )
@@ -122,6 +126,37 @@ module Carson
122
126
  text.empty? ? "unknown error" : text
123
127
  end
124
128
 
129
+ # Attempts git branch -D. If blocked by a worktree, safely removes the worktree
130
+ # first (no --force — refuses if worktree has uncommitted changes) and retries.
131
+ def force_delete_local_branch( branch: )
132
+ stdout, stderr, success, = git_run( "branch", "-D", branch )
133
+ return [ stdout, stderr, success ] if success
134
+ return [ stdout, stderr, false ] unless worktree_blocked_error?( error_text: stderr )
135
+
136
+ wt_path = worktree_path_for_branch( branch: branch )
137
+ return [ stdout, stderr, false ] if wt_path.nil?
138
+
139
+ rm_stdout, rm_stderr, rm_success, = git_run( "worktree", "remove", wt_path )
140
+ unless rm_success
141
+ error_text = rm_stderr.to_s.strip
142
+ puts_verbose "skip_worktree_remove: #{wt_path} (branch=#{branch}) reason=#{error_text}"
143
+ return [ stdout, stderr, false ]
144
+ end
145
+ puts_verbose "worktree_removed_for_prune: #{wt_path} (branch=#{branch})"
146
+
147
+ git_run( "branch", "-D", branch )
148
+ end
149
+
150
+ def worktree_blocked_error?( error_text: )
151
+ error_text.to_s.downcase.include?( "used by worktree" )
152
+ end
153
+
154
+ # Returns the worktree path for a branch, or nil if not checked out in any worktree.
155
+ def worktree_path_for_branch( branch: )
156
+ entry = worktree_list.find { |wt| wt.fetch( :branch, nil ) == branch }
157
+ entry&.fetch( :path, nil )
158
+ end
159
+
125
160
  # Detects local branches whose upstream tracking is marked [gone] after fetch --prune.
126
161
  def stale_local_branches
127
162
  git_capture!( "for-each-ref", "--format=%(refname:short)\t%(upstream:short)\t%(upstream:track)", "refs/heads" ).lines.map do |line|
@@ -218,7 +253,7 @@ module Carson
218
253
  return :skipped
219
254
  end
220
255
 
221
- force_stdout, force_stderr, force_success, = git_run( "branch", "-D", branch )
256
+ force_stdout, force_stderr, force_success = force_delete_local_branch( branch: branch )
222
257
  unless force_success
223
258
  error_text = normalise_branch_delete_error( error_text: force_stderr )
224
259
  puts_verbose "fail_delete_absorbed_branch: #{branch} reason=#{error_text}"
@@ -287,7 +322,7 @@ module Carson
287
322
  return :skipped
288
323
  end
289
324
 
290
- force_stdout, force_stderr, force_success, = git_run( "branch", "-D", branch )
325
+ force_stdout, force_stderr, force_success = force_delete_local_branch( branch: branch )
291
326
  if force_success
292
327
  out.print force_stdout if verbose? && !force_stdout.empty?
293
328
  puts_verbose "deleted_orphan_branch: #{branch} merged_pr=#{merged_pr.fetch( :url )}"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carson
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.31.0
4
+ version: 2.32.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang