carson 2.32.0 → 2.33.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 +4 -4
- data/RELEASE.md +15 -0
- data/VERSION +1 -1
- data/lib/carson/cli.rb +3 -2
- data/lib/carson/runtime/local/prune.rb +7 -13
- data/lib/carson/runtime/local/template.rb +3 -1
- data/lib/carson/runtime/local/worktree.rb +15 -4
- 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: 83e70e6824d3de6da2d444aa9d0b2859f3710e357f87cdcf65cecc512ff63fee
|
|
4
|
+
data.tar.gz: 6c09bd64199ab8ee6f7fa14722b670a123c7cb051ea2937c1e2bbfddb696fded
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a6570b397a73c27aab1d33e044bd28f56d1f643e57e76b40d3bbd5cdd1ff5da2779467980c9f595c6a25cbe2524cf5ff6508050a2736677ebb94079434eb749
|
|
7
|
+
data.tar.gz: 33d29172314ab043a5a38122b91fac3c150b3d846ef113cff953fefec5f56f8c35af1290a48ef951fe1154de4caf749cd779daf98c09c939518c0e64f7a72ecd
|
data/RELEASE.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.33.0 — Safe Worktree Remove
|
|
9
|
+
|
|
10
|
+
### What changed
|
|
11
|
+
|
|
12
|
+
- **`carson worktree remove` no longer force-removes by default.** If the worktree has uncommitted or untracked changes, Carson refuses with a clear message instead of silently discarding work. Pass `--force` to override when you intentionally want to discard changes.
|
|
13
|
+
- **Template sync cleanup is safer.** Carson's internal template propagation tries safe worktree removal first, falling back to `--force` only for its own ephemeral sync worktree.
|
|
14
|
+
|
|
15
|
+
### UX
|
|
16
|
+
|
|
17
|
+
- Dirty worktree removal now shows the worktree name and actionable guidance: "Commit or discard changes first, or use --force to override."
|
|
18
|
+
|
|
19
|
+
### Migration
|
|
20
|
+
|
|
21
|
+
- No action required. Existing `carson worktree remove` calls without `--force` will now refuse on dirty worktrees instead of silently destroying uncommitted work.
|
|
22
|
+
|
|
8
23
|
## 2.32.0 — Worktree-Aware Pruning
|
|
9
24
|
|
|
10
25
|
### What changed
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.33.0
|
data/lib/carson/cli.rb
CHANGED
|
@@ -175,12 +175,13 @@ module Carson
|
|
|
175
175
|
|
|
176
176
|
case action
|
|
177
177
|
when "remove"
|
|
178
|
+
force = argv.delete( "--force" ) ? true : false
|
|
178
179
|
worktree_path = argv.shift
|
|
179
180
|
if worktree_path.to_s.strip.empty?
|
|
180
181
|
err.puts "#{BADGE} Missing path for worktree remove. Use: carson worktree remove <name-or-path>"
|
|
181
182
|
return { command: :invalid }
|
|
182
183
|
end
|
|
183
|
-
{ command: "worktree:remove", worktree_path: worktree_path }
|
|
184
|
+
{ command: "worktree:remove", worktree_path: worktree_path, force: force }
|
|
184
185
|
else
|
|
185
186
|
err.puts "#{BADGE} Unknown worktree subcommand: #{action}. Use: carson worktree remove <name-or-path>"
|
|
186
187
|
{ command: :invalid }
|
|
@@ -276,7 +277,7 @@ module Carson
|
|
|
276
277
|
when "prune:all"
|
|
277
278
|
runtime.prune_all!
|
|
278
279
|
when "worktree:remove"
|
|
279
|
-
runtime.worktree_remove!( worktree_path: parsed.fetch( :worktree_path ) )
|
|
280
|
+
runtime.worktree_remove!( worktree_path: parsed.fetch( :worktree_path ), force: parsed.fetch( :force, false ) )
|
|
280
281
|
when "onboard"
|
|
281
282
|
runtime.onboard!
|
|
282
283
|
when "refresh"
|
|
@@ -126,25 +126,19 @@ module Carson
|
|
|
126
126
|
text.empty? ? "unknown error" : text
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
# Attempts git branch -D. If blocked by a worktree,
|
|
130
|
-
#
|
|
129
|
+
# Attempts git branch -D. If blocked by a worktree, skips with a diagnostic —
|
|
130
|
+
# prune never removes worktrees because another session may own them.
|
|
131
131
|
def force_delete_local_branch( branch: )
|
|
132
132
|
stdout, stderr, success, = git_run( "branch", "-D", branch )
|
|
133
133
|
return [ stdout, stderr, success ] if success
|
|
134
|
-
return [ stdout, stderr, false ] unless worktree_blocked_error?( error_text: stderr )
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 ]
|
|
135
|
+
if worktree_blocked_error?( error_text: stderr )
|
|
136
|
+
wt_path = worktree_path_for_branch( branch: branch )
|
|
137
|
+
hint = wt_path ? "run: carson worktree remove #{File.basename( wt_path )}" : "remove the worktree first"
|
|
138
|
+
puts_verbose "skip_worktree_blocked: #{branch} (#{hint})"
|
|
144
139
|
end
|
|
145
|
-
puts_verbose "worktree_removed_for_prune: #{wt_path} (branch=#{branch})"
|
|
146
140
|
|
|
147
|
-
|
|
141
|
+
[ stdout, stderr, false ]
|
|
148
142
|
end
|
|
149
143
|
|
|
150
144
|
def worktree_blocked_error?( error_text: )
|
|
@@ -244,7 +244,9 @@ module Carson
|
|
|
244
244
|
end
|
|
245
245
|
|
|
246
246
|
def template_propagate_cleanup!( worktree_dir: )
|
|
247
|
-
|
|
247
|
+
# Try safe removal first; fall back to force only for Carson's own sync worktree.
|
|
248
|
+
_, _, safe_success, = git_run( "worktree", "remove", worktree_dir )
|
|
249
|
+
git_run( "worktree", "remove", "--force", worktree_dir ) unless safe_success
|
|
248
250
|
git_run( "branch", "-D", TEMPLATE_SYNC_BRANCH )
|
|
249
251
|
puts_verbose "template_propagate: worktree and local branch cleaned up"
|
|
250
252
|
rescue StandardError => e
|
|
@@ -3,7 +3,9 @@ module Carson
|
|
|
3
3
|
module Local
|
|
4
4
|
# Safe worktree lifecycle management for coding agents.
|
|
5
5
|
# Enforces the teardown order: exit worktree → git worktree remove → branch cleanup.
|
|
6
|
-
|
|
6
|
+
# Never forces removal — if the worktree has uncommitted changes, refuses unless
|
|
7
|
+
# the user explicitly passes force: true via CLI --force flag.
|
|
8
|
+
def worktree_remove!( worktree_path:, force: false )
|
|
7
9
|
fingerprint_status = block_if_outsider_fingerprints!
|
|
8
10
|
return fingerprint_status unless fingerprint_status.nil?
|
|
9
11
|
|
|
@@ -17,14 +19,23 @@ module Carson
|
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
branch = worktree_branch( path: resolved_path )
|
|
20
|
-
puts_verbose "worktree_remove: path=#{resolved_path} branch=#{branch}"
|
|
22
|
+
puts_verbose "worktree_remove: path=#{resolved_path} branch=#{branch} force=#{force}"
|
|
21
23
|
|
|
22
24
|
# Step 1: remove the worktree (directory + git registration).
|
|
23
|
-
|
|
25
|
+
# Try safe removal first. Only use --force if the user explicitly requested it.
|
|
26
|
+
rm_args = [ "worktree", "remove" ]
|
|
27
|
+
rm_args << "--force" if force
|
|
28
|
+
rm_args << resolved_path
|
|
29
|
+
rm_stdout, rm_stderr, rm_success, = git_run( *rm_args )
|
|
24
30
|
unless rm_success
|
|
25
31
|
error_text = rm_stderr.to_s.strip
|
|
26
32
|
error_text = "unable to remove worktree" if error_text.empty?
|
|
27
|
-
|
|
33
|
+
if !force && ( error_text.downcase.include?( "untracked" ) || error_text.downcase.include?( "modified" ) )
|
|
34
|
+
puts_line "Worktree has uncommitted changes: #{File.basename( resolved_path )}"
|
|
35
|
+
puts_line " Commit or discard changes first, or use --force to override."
|
|
36
|
+
else
|
|
37
|
+
puts_line "ERROR: #{error_text}"
|
|
38
|
+
end
|
|
28
39
|
return EXIT_ERROR
|
|
29
40
|
end
|
|
30
41
|
puts_verbose "worktree_removed: #{resolved_path}"
|