carson 3.4.0 → 3.5.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 +16 -0
- data/VERSION +1 -1
- data/lib/carson/cli.rb +13 -2
- data/lib/carson/runtime/local/sync.rb +56 -12
- 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: e8fc2b54698568aaec7d7f4ec39ecd32f56f44a395555a34099b24ec52a1d608
|
|
4
|
+
data.tar.gz: e3b0a5a14c733d6b2e9f5c08475d89a53f0a3d1faf8a2fb85ed72bcb76f662ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d3733f98a23dffdc69c1ebde71e94595c22e662f5ead86b97988fa3f5cf0e8daf56806deb82ceb1d034e0913640d9a8b7cafc26992f94e43dd96a3e304d6812
|
|
7
|
+
data.tar.gz: eb44eca84a943a461668b478e2bed295b3a73ee7e916cc5d85a837c52f5be09326b369e07a99812d4ffdc883914511d18c7f0489e151f3d4d66afe8ddaf97402
|
data/RELEASE.md
CHANGED
|
@@ -5,6 +5,22 @@ 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.5.0 — Sync JSON + Recovery
|
|
9
|
+
|
|
10
|
+
### What changed
|
|
11
|
+
|
|
12
|
+
- **`carson sync --json`** — machine-readable JSON output for the sync command. The JSON envelope includes `command`, `status`, `ahead`, `behind`, `main_branch`, `remote`, `exit_code`, and on failure: `error` and `recovery`.
|
|
13
|
+
- **Recovery-aware sync errors** — dirty working tree now includes a recovery command in both human (`Recovery: ...`) and JSON output.
|
|
14
|
+
|
|
15
|
+
### UX
|
|
16
|
+
|
|
17
|
+
- JSON output suppresses git command output (fetch, pull) to keep the JSON envelope clean.
|
|
18
|
+
- Human output remains unchanged when `--json` is not passed.
|
|
19
|
+
|
|
20
|
+
### Migration
|
|
21
|
+
|
|
22
|
+
- No breaking changes. `carson sync` without `--json` behaves identically to 3.4.0.
|
|
23
|
+
|
|
8
24
|
## 3.4.0 — Audit JSON
|
|
9
25
|
|
|
10
26
|
### What changed
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.5.0
|
data/lib/carson/cli.rb
CHANGED
|
@@ -53,7 +53,7 @@ module Carson
|
|
|
53
53
|
|
|
54
54
|
def self.build_parser
|
|
55
55
|
OptionParser.new do |opts|
|
|
56
|
-
opts.banner = "Usage: carson [status [--json]|setup|audit [--json]|sync|deliver [--merge] [--json] [--title T] [--body-file F]|prune [--all]|worktree create|done|remove <name>|onboard|refresh [--all]|offboard|template check|apply|review gate|sweep|govern [--dry-run] [--json] [--loop SECONDS]|version]"
|
|
56
|
+
opts.banner = "Usage: carson [status [--json]|setup|audit [--json]|sync [--json]|deliver [--merge] [--json] [--title T] [--body-file F]|prune [--all]|worktree create|done|remove <name>|onboard|refresh [--all]|offboard|template check|apply|review gate|sweep|govern [--dry-run] [--json] [--loop SECONDS]|version]"
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -90,6 +90,8 @@ module Carson
|
|
|
90
90
|
parse_named_subcommand( command: command, usage: "gate|sweep", argv: argv, parser: parser, err: err )
|
|
91
91
|
when "audit"
|
|
92
92
|
parse_audit_command( argv: argv, err: err )
|
|
93
|
+
when "sync"
|
|
94
|
+
parse_sync_command( argv: argv, err: err )
|
|
93
95
|
when "status"
|
|
94
96
|
parse_status_command( argv: argv, err: err )
|
|
95
97
|
when "deliver"
|
|
@@ -253,6 +255,15 @@ module Carson
|
|
|
253
255
|
{ command: "audit", json: json_flag }
|
|
254
256
|
end
|
|
255
257
|
|
|
258
|
+
def self.parse_sync_command( argv:, err: )
|
|
259
|
+
json_flag = argv.delete( "--json" ) ? true : false
|
|
260
|
+
unless argv.empty?
|
|
261
|
+
err.puts "#{BADGE} Unexpected arguments for sync: #{argv.join( ' ' )}"
|
|
262
|
+
return { command: :invalid }
|
|
263
|
+
end
|
|
264
|
+
{ command: "sync", json: json_flag }
|
|
265
|
+
end
|
|
266
|
+
|
|
256
267
|
def self.parse_status_command( argv:, err: )
|
|
257
268
|
json_flag = argv.delete( "--json" ) ? true : false
|
|
258
269
|
unless argv.empty?
|
|
@@ -334,7 +345,7 @@ module Carson
|
|
|
334
345
|
when "audit"
|
|
335
346
|
runtime.audit!( json_output: parsed.fetch( :json, false ) )
|
|
336
347
|
when "sync"
|
|
337
|
-
runtime.sync!
|
|
348
|
+
runtime.sync!( json_output: parsed.fetch( :json, false ) )
|
|
338
349
|
when "prune"
|
|
339
350
|
runtime.prune!
|
|
340
351
|
when "prune:all"
|
|
@@ -1,39 +1,83 @@
|
|
|
1
|
+
# Syncs local main branch with remote main.
|
|
2
|
+
# Supports --json for machine-readable structured output.
|
|
1
3
|
module Carson
|
|
2
4
|
class Runtime
|
|
3
5
|
module Local
|
|
4
|
-
def sync!
|
|
6
|
+
def sync!( json_output: false )
|
|
5
7
|
fingerprint_status = block_if_outsider_fingerprints!
|
|
6
8
|
return fingerprint_status unless fingerprint_status.nil?
|
|
7
9
|
|
|
8
10
|
unless working_tree_clean?
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
return sync_finish(
|
|
12
|
+
result: { command: "sync", status: "block", error: "working tree is dirty", recovery: "git add -A && git commit, then carson sync" },
|
|
13
|
+
exit_code: EXIT_BLOCK, json_output: json_output
|
|
14
|
+
)
|
|
11
15
|
end
|
|
12
16
|
start_branch = current_branch
|
|
13
17
|
switched = false
|
|
14
|
-
|
|
18
|
+
sync_git!( "fetch", config.git_remote, "--prune", json_output: json_output )
|
|
15
19
|
if start_branch != config.main_branch
|
|
16
|
-
|
|
20
|
+
sync_git!( "switch", config.main_branch, json_output: json_output )
|
|
17
21
|
switched = true
|
|
18
22
|
end
|
|
19
|
-
|
|
23
|
+
sync_git!( "pull", "--ff-only", config.git_remote, config.main_branch, json_output: json_output )
|
|
20
24
|
ahead_count, behind_count, error_text = main_sync_counts
|
|
21
25
|
if error_text
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
return sync_finish(
|
|
27
|
+
result: { command: "sync", status: "block", error: "unable to verify main sync state (#{error_text})" },
|
|
28
|
+
exit_code: EXIT_BLOCK, json_output: json_output
|
|
29
|
+
)
|
|
24
30
|
end
|
|
25
31
|
if ahead_count.zero? && behind_count.zero?
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
return sync_finish(
|
|
33
|
+
result: { command: "sync", status: "ok", ahead: 0, behind: 0, main_branch: config.main_branch, remote: config.git_remote },
|
|
34
|
+
exit_code: EXIT_OK, json_output: json_output
|
|
35
|
+
)
|
|
28
36
|
end
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
sync_finish(
|
|
38
|
+
result: { command: "sync", status: "block", ahead: ahead_count, behind: behind_count, main_branch: config.main_branch, remote: config.git_remote, error: "local #{config.main_branch} still diverges" },
|
|
39
|
+
exit_code: EXIT_BLOCK, json_output: json_output
|
|
40
|
+
)
|
|
31
41
|
ensure
|
|
32
42
|
git_system!( "switch", start_branch ) if switched && branch_exists?( branch_name: start_branch )
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
private
|
|
36
46
|
|
|
47
|
+
# Runs a git command, suppressing stdout/stderr in JSON mode to keep output clean.
|
|
48
|
+
def sync_git!( *args, json_output: false )
|
|
49
|
+
if json_output
|
|
50
|
+
_, stderr_text, success, = git_run( *args )
|
|
51
|
+
raise "git #{args.join( ' ' )} failed: #{stderr_text.to_s.strip}" unless success
|
|
52
|
+
else
|
|
53
|
+
git_system!( *args )
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Unified output for sync results — JSON or human-readable.
|
|
58
|
+
def sync_finish( result:, exit_code:, json_output: )
|
|
59
|
+
result[ :exit_code ] = exit_code
|
|
60
|
+
|
|
61
|
+
if json_output
|
|
62
|
+
out.puts JSON.pretty_generate( result )
|
|
63
|
+
else
|
|
64
|
+
print_sync_human( result: result )
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
exit_code
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Human-readable output for sync results.
|
|
71
|
+
def print_sync_human( result: )
|
|
72
|
+
if result[ :error ]
|
|
73
|
+
puts_line "BLOCK: #{result[ :error ]}."
|
|
74
|
+
puts_line " Recovery: #{result[ :recovery ]}" if result[ :recovery ]
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
puts_line "OK: local #{result[ :main_branch ]} is now in sync with #{result[ :remote ]}/#{result[ :main_branch ]}."
|
|
79
|
+
end
|
|
80
|
+
|
|
37
81
|
# Returns ahead/behind counts for local main versus configured remote main.
|
|
38
82
|
def main_sync_counts
|
|
39
83
|
target = "#{config.main_branch}...#{config.git_remote}/#{config.main_branch}"
|