carson 3.10.4 → 3.10.5
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 +4 -4
- data/RELEASE.md +4 -5
- data/VERSION +1 -1
- data/lib/carson/runtime/local/prune.rb +14 -10
- data/lib/carson/runtime/local/worktree.rb +22 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 29198792c7eb5bb9c02c43fc31d6a2ba04fc1430684e978cc365c2e74b47f810
|
|
4
|
+
data.tar.gz: b0e164e172315cc707017a9c82e83328fdb1e84a5e4f983aa5aeabc9caae1b04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45361da34684eaf4d399f7d40eba060a63f3c364f41ed890ac448f6ffeeb4d886961f451ce393adec495240ffc5bff5bc95399b59d7afd08aaba0eadf0579fde
|
|
7
|
+
data.tar.gz: b827c09fcf9cef558f237471abd328906ecda337f2bdef04a7991ee2547b2a31437840eb651d04e1ca659c1d55b891e84890cf57b857247240fd3a5ecd81f71c
|
data/RELEASE.md
CHANGED
|
@@ -5,17 +5,16 @@ 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
|
-
## 3.10.
|
|
8
|
+
## 3.10.5
|
|
9
9
|
|
|
10
10
|
### What changed
|
|
11
11
|
|
|
12
|
-
- **
|
|
13
|
-
-
|
|
14
|
-
- **Fix resolve path from inside worktrees** — `resolve_worktree_path` now uses `main_worktree_root` instead of `repo_root` for bare-name resolution. Previously, calling `carson worktree remove <name>` from inside a worktree would look in the wrong directory.
|
|
12
|
+
- **CWD guard for prune** — `carson prune` now proactively detects when the process CWD is inside a worktree and skips that worktree's branch in all prune paths (stale, orphan, absorbed). Previously, prune relied on git's own refusal to delete a branch checked out in a worktree — accidental protection, not principled safety. The new guard matches the same CWD-awareness that `worktree remove` already has.
|
|
13
|
+
- **`cwd_worktree_branch` helper** — new method that finds the branch checked out in the worktree containing the process CWD. Uses longest-path matching because worktree directories live inside the main repo tree (`.claude/worktrees/`).
|
|
15
14
|
|
|
16
15
|
### Migration
|
|
17
16
|
|
|
18
|
-
- No breaking changes.
|
|
17
|
+
- No breaking changes. New safety guard — previously git-protected operations now fail earlier with clearer diagnostics.
|
|
19
18
|
|
|
20
19
|
## 3.10.3
|
|
21
20
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.10.
|
|
1
|
+
3.10.5
|
|
@@ -20,16 +20,17 @@ module Carson
|
|
|
20
20
|
|
|
21
21
|
prune_git!( "fetch", config.git_remote, "--prune", json_output: json_output )
|
|
22
22
|
active_branch = current_branch
|
|
23
|
+
cwd_branch = cwd_worktree_branch
|
|
23
24
|
counters = { deleted: 0, skipped: 0 }
|
|
24
25
|
branches = []
|
|
25
26
|
|
|
26
27
|
stale_branches = stale_local_branches
|
|
27
|
-
prune_stale_branch_entries( stale_branches: stale_branches, active_branch: active_branch, counters: counters, branches: branches )
|
|
28
|
+
prune_stale_branch_entries( stale_branches: stale_branches, active_branch: active_branch, cwd_branch: cwd_branch, counters: counters, branches: branches )
|
|
28
29
|
|
|
29
|
-
orphan_branches = orphan_local_branches( active_branch: active_branch )
|
|
30
|
+
orphan_branches = orphan_local_branches( active_branch: active_branch, cwd_branch: cwd_branch )
|
|
30
31
|
prune_orphan_branch_entries( orphan_branches: orphan_branches, counters: counters, branches: branches )
|
|
31
32
|
|
|
32
|
-
absorbed_branches = absorbed_local_branches( active_branch: active_branch )
|
|
33
|
+
absorbed_branches = absorbed_local_branches( active_branch: active_branch, cwd_branch: cwd_branch )
|
|
33
34
|
prune_absorbed_branch_entries( absorbed_branches: absorbed_branches, counters: counters, branches: branches )
|
|
34
35
|
|
|
35
36
|
prune_finish(
|
|
@@ -90,27 +91,28 @@ module Carson
|
|
|
90
91
|
end
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
def prune_stale_branch_entries( stale_branches:, active_branch:, counters: { deleted: 0, skipped: 0 }, branches: [] )
|
|
94
|
+
def prune_stale_branch_entries( stale_branches:, active_branch:, cwd_branch: nil, counters: { deleted: 0, skipped: 0 }, branches: [] )
|
|
94
95
|
stale_branches.each do |entry|
|
|
95
|
-
result = prune_stale_branch_entry( entry: entry, active_branch: active_branch )
|
|
96
|
+
result = prune_stale_branch_entry( entry: entry, active_branch: active_branch, cwd_branch: cwd_branch )
|
|
96
97
|
counters[ result.fetch( :action ) ] += 1
|
|
97
98
|
branches << result
|
|
98
99
|
end
|
|
99
100
|
counters
|
|
100
101
|
end
|
|
101
102
|
|
|
102
|
-
def prune_stale_branch_entry( entry:, active_branch: )
|
|
103
|
+
def prune_stale_branch_entry( entry:, active_branch:, cwd_branch: nil )
|
|
103
104
|
branch = entry.fetch( :branch )
|
|
104
105
|
upstream = entry.fetch( :upstream )
|
|
105
106
|
return prune_skip_stale_branch( type: :protected, branch: branch, upstream: upstream ) if config.protected_branches.include?( branch )
|
|
106
107
|
return prune_skip_stale_branch( type: :current, branch: branch, upstream: upstream ) if branch == active_branch
|
|
108
|
+
return prune_skip_stale_branch( type: :cwd_worktree, branch: branch, upstream: upstream ) if cwd_branch && branch == cwd_branch
|
|
107
109
|
|
|
108
110
|
prune_delete_stale_branch( branch: branch, upstream: upstream )
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
def prune_skip_stale_branch( type:, branch:, upstream: )
|
|
112
|
-
reason =
|
|
113
|
-
status =
|
|
114
|
+
reason = { protected: "protected branch", current: "current branch", cwd_worktree: "checked out in CWD worktree" }.fetch( type, type.to_s )
|
|
115
|
+
status = { protected: "skip_protected_branch", current: "skip_current_branch", cwd_worktree: "skip_cwd_worktree_branch" }.fetch( type, "skip_#{type}" )
|
|
114
116
|
puts_verbose "#{status}: #{branch} (upstream=#{upstream})"
|
|
115
117
|
{ action: :skipped, branch: branch, upstream: upstream, type: "stale", reason: reason }
|
|
116
118
|
end
|
|
@@ -208,7 +210,7 @@ module Carson
|
|
|
208
210
|
end
|
|
209
211
|
|
|
210
212
|
# Detects local branches with no upstream tracking ref — candidates for orphan pruning.
|
|
211
|
-
def orphan_local_branches( active_branch: )
|
|
213
|
+
def orphan_local_branches( active_branch:, cwd_branch: nil )
|
|
212
214
|
git_capture!( "for-each-ref", "--format=%(refname:short)\t%(upstream:short)", "refs/heads" ).lines.filter_map do |line|
|
|
213
215
|
branch, upstream = line.strip.split( "\t", 2 )
|
|
214
216
|
branch = branch.to_s.strip
|
|
@@ -217,6 +219,7 @@ module Carson
|
|
|
217
219
|
next unless upstream.empty?
|
|
218
220
|
next if config.protected_branches.include?( branch )
|
|
219
221
|
next if branch == active_branch
|
|
222
|
+
next if cwd_branch && branch == cwd_branch
|
|
220
223
|
next if branch == TEMPLATE_SYNC_BRANCH
|
|
221
224
|
|
|
222
225
|
branch
|
|
@@ -226,7 +229,7 @@ module Carson
|
|
|
226
229
|
# Detects local branches whose upstream still exists but whose content is already on main.
|
|
227
230
|
# Two-step evidence: (1) find the merge-base, (2) verify every file the branch changed
|
|
228
231
|
# relative to the merge-base has identical content on main.
|
|
229
|
-
def absorbed_local_branches( active_branch: )
|
|
232
|
+
def absorbed_local_branches( active_branch:, cwd_branch: nil )
|
|
230
233
|
git_capture!( "for-each-ref", "--format=%(refname:short)\t%(upstream:short)\t%(upstream:track)", "refs/heads" ).lines.filter_map do |line|
|
|
231
234
|
branch, upstream, track = line.strip.split( "\t", 3 )
|
|
232
235
|
branch = branch.to_s.strip
|
|
@@ -237,6 +240,7 @@ module Carson
|
|
|
237
240
|
next if track.include?( "gone" )
|
|
238
241
|
next if config.protected_branches.include?( branch )
|
|
239
242
|
next if branch == active_branch
|
|
243
|
+
next if cwd_branch && branch == cwd_branch
|
|
240
244
|
next if branch == TEMPLATE_SYNC_BRANCH
|
|
241
245
|
|
|
242
246
|
next unless branch_absorbed_into_main?( branch: branch )
|
|
@@ -299,6 +299,28 @@ module Carson
|
|
|
299
299
|
nil
|
|
300
300
|
end
|
|
301
301
|
|
|
302
|
+
# Returns the branch checked out in the worktree that contains the process CWD,
|
|
303
|
+
# or nil if CWD is not inside any worktree. Used by prune to proactively
|
|
304
|
+
# protect the CWD worktree's branch from deletion.
|
|
305
|
+
# Matches the longest (most specific) path because worktree directories
|
|
306
|
+
# live under the main repo tree (.claude/worktrees/).
|
|
307
|
+
def cwd_worktree_branch
|
|
308
|
+
cwd = realpath_safe( Dir.pwd )
|
|
309
|
+
best_branch = nil
|
|
310
|
+
best_length = -1
|
|
311
|
+
worktree_list.each do |wt|
|
|
312
|
+
wt_path = wt.fetch( :path )
|
|
313
|
+
normalised = File.join( wt_path, "" )
|
|
314
|
+
if ( cwd == wt_path || cwd.start_with?( normalised ) ) && wt_path.length > best_length
|
|
315
|
+
best_branch = wt.fetch( :branch, nil )
|
|
316
|
+
best_length = wt_path.length
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
best_branch
|
|
320
|
+
rescue StandardError
|
|
321
|
+
nil
|
|
322
|
+
end
|
|
323
|
+
|
|
302
324
|
# Returns the main (non-worktree) repository root.
|
|
303
325
|
# Uses git-common-dir to find the shared .git directory, then takes its parent.
|
|
304
326
|
# Falls back to repo_root if detection fails.
|