carson 3.2.0 → 3.4.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 +34 -0
- data/VERSION +1 -1
- data/lib/carson/cli.rb +19 -5
- data/lib/carson/runtime/audit.rb +47 -10
- data/lib/carson/runtime/deliver.rb +94 -29
- 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: 514509a6422a4c2237a48feac1bff1c10e4705afe62fb574d400b8364cf131ba
|
|
4
|
+
data.tar.gz: 1f7bbf53a01ee92d8a97d62d0a7957bb823c0aabd70e31a7e27e37de8ab3b8c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a536b8a85ba6b2a737ce913485e3d58b5672b6b13af40261b181dd234bf1052ffa93a5f9b51307d9ce7e433e588b92ce294e88118b758747d4bb5cbad898c6c5
|
|
7
|
+
data.tar.gz: d476ba9ed57c7c97967c0cb2fab8b8390debfa196d657ae6d2e6c51e143248f8160bc543eb0d9b4e5c3c434ba4a25aa16b8af9aa6ec3b9cfcf5b7c974cd8fa9e
|
data/RELEASE.md
CHANGED
|
@@ -5,6 +5,40 @@ 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.4.0 — Audit JSON
|
|
9
|
+
|
|
10
|
+
### What changed
|
|
11
|
+
|
|
12
|
+
- **`carson audit --json`** — machine-readable JSON output for the audit command. The JSON envelope includes `command`, `status`, `branch`, `hooks`, `main_sync`, `pr`, `checks`, `baseline`, `problems`, and `exit_code`. Agents can parse audit results programmatically instead of regex-matching human-readable text.
|
|
13
|
+
|
|
14
|
+
### UX
|
|
15
|
+
|
|
16
|
+
- JSON output is only produced when `--json` is passed; default human-readable output is unchanged.
|
|
17
|
+
- The `problems` array contains the same concise problem strings shown in non-verbose human output, making it easy for agents to surface actionable issues.
|
|
18
|
+
- `hooks.status` is `"ok"` or `"mismatch"`, `main_sync.status` is `"ok"`, `"ahead"`, `"behind"`, or `"unknown"`.
|
|
19
|
+
|
|
20
|
+
### Migration
|
|
21
|
+
|
|
22
|
+
- No breaking changes. `carson audit` without `--json` behaves identically to 3.3.0.
|
|
23
|
+
- The `audit!` method now accepts `json_output:` keyword argument (default `false`).
|
|
24
|
+
|
|
25
|
+
## 3.3.0 — Deliver Refinements
|
|
26
|
+
|
|
27
|
+
### What changed
|
|
28
|
+
|
|
29
|
+
- **`carson deliver --json`** — machine-readable JSON output for agent consumption. The JSON envelope includes `command`, `branch`, `pr_number`, `pr_url`, `ci`, `merged`, `exit_code`, and — on failure — `error` and `recovery` fields.
|
|
30
|
+
- **Recovery-aware errors** — every deliver error path now includes a concrete `recovery` command showing the user exactly what to run next. Human output shows `Recovery: <command>`, JSON output includes a `recovery` field.
|
|
31
|
+
|
|
32
|
+
### UX
|
|
33
|
+
|
|
34
|
+
- JSON output uses `JSON.pretty_generate` for readability when inspected by humans.
|
|
35
|
+
- Recovery commands are context-specific: push failures suggest `git pull --rebase && git push`, main-branch errors suggest `git checkout -b <branch>`, CI failures suggest `gh pr checks` with re-deliver.
|
|
36
|
+
- Human output for CI pending and CI fail states now includes recovery guidance.
|
|
37
|
+
|
|
38
|
+
### Migration
|
|
39
|
+
|
|
40
|
+
- No breaking changes. `carson deliver` without `--json` behaves identically to 3.2.0.
|
|
41
|
+
|
|
8
42
|
## 3.2.0 — Deliver
|
|
9
43
|
|
|
10
44
|
### What changed
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.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|sync|deliver [--merge] [--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|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
|
|
|
@@ -88,6 +88,8 @@ module Carson
|
|
|
88
88
|
parse_worktree_subcommand( argv: argv, parser: parser, err: err )
|
|
89
89
|
when "review"
|
|
90
90
|
parse_named_subcommand( command: command, usage: "gate|sweep", argv: argv, parser: parser, err: err )
|
|
91
|
+
when "audit"
|
|
92
|
+
parse_audit_command( argv: argv, err: err )
|
|
91
93
|
when "status"
|
|
92
94
|
parse_status_command( argv: argv, err: err )
|
|
93
95
|
when "deliver"
|
|
@@ -242,6 +244,15 @@ module Carson
|
|
|
242
244
|
{ command: :invalid }
|
|
243
245
|
end
|
|
244
246
|
|
|
247
|
+
def self.parse_audit_command( argv:, err: )
|
|
248
|
+
json_flag = argv.delete( "--json" ) ? true : false
|
|
249
|
+
unless argv.empty?
|
|
250
|
+
err.puts "#{BADGE} Unexpected arguments for audit: #{argv.join( ' ' )}"
|
|
251
|
+
return { command: :invalid }
|
|
252
|
+
end
|
|
253
|
+
{ command: "audit", json: json_flag }
|
|
254
|
+
end
|
|
255
|
+
|
|
245
256
|
def self.parse_status_command( argv:, err: )
|
|
246
257
|
json_flag = argv.delete( "--json" ) ? true : false
|
|
247
258
|
unless argv.empty?
|
|
@@ -252,10 +263,11 @@ module Carson
|
|
|
252
263
|
end
|
|
253
264
|
|
|
254
265
|
def self.parse_deliver_command( argv:, err: )
|
|
255
|
-
options = { merge: false, title: nil, body_file: nil }
|
|
266
|
+
options = { merge: false, json: false, title: nil, body_file: nil }
|
|
256
267
|
deliver_parser = OptionParser.new do |opts|
|
|
257
|
-
opts.banner = "Usage: carson deliver [--merge] [--title TITLE] [--body-file PATH]"
|
|
268
|
+
opts.banner = "Usage: carson deliver [--merge] [--json] [--title TITLE] [--body-file PATH]"
|
|
258
269
|
opts.on( "--merge", "Also merge the PR if CI passes" ) { options[ :merge ] = true }
|
|
270
|
+
opts.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
259
271
|
opts.on( "--title TITLE", "PR title (defaults to branch name)" ) { |v| options[ :title ] = v }
|
|
260
272
|
opts.on( "--body-file PATH", "File containing PR body text" ) { |v| options[ :body_file ] = v }
|
|
261
273
|
end
|
|
@@ -268,6 +280,7 @@ module Carson
|
|
|
268
280
|
{
|
|
269
281
|
command: "deliver",
|
|
270
282
|
merge: options.fetch( :merge ),
|
|
283
|
+
json: options.fetch( :json ),
|
|
271
284
|
title: options[ :title ],
|
|
272
285
|
body_file: options[ :body_file ]
|
|
273
286
|
}
|
|
@@ -319,7 +332,7 @@ module Carson
|
|
|
319
332
|
when "setup"
|
|
320
333
|
runtime.setup!( cli_choices: parsed.fetch( :cli_choices, {} ) )
|
|
321
334
|
when "audit"
|
|
322
|
-
runtime.audit!
|
|
335
|
+
runtime.audit!( json_output: parsed.fetch( :json, false ) )
|
|
323
336
|
when "sync"
|
|
324
337
|
runtime.sync!
|
|
325
338
|
when "prune"
|
|
@@ -348,7 +361,8 @@ module Carson
|
|
|
348
361
|
runtime.deliver!(
|
|
349
362
|
merge: parsed.fetch( :merge, false ),
|
|
350
363
|
title: parsed.fetch( :title, nil ),
|
|
351
|
-
body_file: parsed.fetch( :body_file, nil )
|
|
364
|
+
body_file: parsed.fetch( :body_file, nil ),
|
|
365
|
+
json_output: parsed.fetch( :json, false )
|
|
352
366
|
)
|
|
353
367
|
when "review:gate"
|
|
354
368
|
runtime.review_gate!
|
data/lib/carson/runtime/audit.rb
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
# Pre-commit audit — checks hooks, main sync, PR checks, and CI baseline.
|
|
2
|
+
# Exits with EXIT_BLOCK when policy violations are found.
|
|
3
|
+
# Supports --json for machine-readable structured output.
|
|
1
4
|
require "cgi"
|
|
2
5
|
|
|
3
6
|
module Carson
|
|
4
7
|
class Runtime
|
|
5
8
|
module Audit
|
|
6
|
-
def audit!
|
|
9
|
+
def audit!( json_output: false )
|
|
7
10
|
fingerprint_status = block_if_outsider_fingerprints!
|
|
8
11
|
return fingerprint_status unless fingerprint_status.nil?
|
|
9
12
|
unless head_exists?
|
|
10
|
-
|
|
13
|
+
if json_output
|
|
14
|
+
out.puts JSON.pretty_generate( { command: "audit", status: "skipped", reason: "no commits yet", exit_code: EXIT_OK } )
|
|
15
|
+
else
|
|
16
|
+
puts_line "No commits yet — audit skipped for initial commit."
|
|
17
|
+
end
|
|
11
18
|
return EXIT_OK
|
|
12
19
|
end
|
|
13
20
|
audit_state = "ok"
|
|
@@ -22,6 +29,7 @@ module Carson
|
|
|
22
29
|
puts_verbose ""
|
|
23
30
|
puts_verbose "[Hooks]"
|
|
24
31
|
hooks_ok = hooks_health_report
|
|
32
|
+
hooks_status = hooks_ok ? "ok" : "mismatch"
|
|
25
33
|
unless hooks_ok
|
|
26
34
|
audit_state = "block"
|
|
27
35
|
audit_concise_problems << "Hooks: mismatch — run carson refresh."
|
|
@@ -29,23 +37,27 @@ module Carson
|
|
|
29
37
|
puts_verbose ""
|
|
30
38
|
puts_verbose "[Main Sync Status]"
|
|
31
39
|
ahead_count, behind_count, main_error = main_sync_counts
|
|
40
|
+
main_sync = { ahead: 0, behind: 0, status: "ok" }
|
|
32
41
|
if main_error
|
|
33
42
|
puts_verbose "main_vs_remote_main: unknown"
|
|
34
43
|
puts_verbose "WARN: unable to calculate main sync status (#{main_error})."
|
|
35
44
|
audit_state = "attention" if audit_state == "ok"
|
|
36
45
|
audit_concise_problems << "Main sync: unable to determine — check remote connectivity."
|
|
46
|
+
main_sync = { ahead: 0, behind: 0, status: "unknown", error: main_error }
|
|
37
47
|
elsif ahead_count.positive?
|
|
38
48
|
puts_verbose "main_vs_remote_main_ahead: #{ahead_count}"
|
|
39
49
|
puts_verbose "main_vs_remote_main_behind: #{behind_count}"
|
|
40
50
|
puts_verbose "ACTION: local #{config.main_branch} is ahead of #{config.git_remote}/#{config.main_branch} by #{ahead_count} commit#{plural_suffix( count: ahead_count )}; reset local drift before commit/push workflows."
|
|
41
51
|
audit_state = "block"
|
|
42
52
|
audit_concise_problems << "Main sync (#{config.git_remote}): ahead by #{ahead_count} — git fetch #{config.git_remote}, or carson setup to switch remote."
|
|
53
|
+
main_sync = { ahead: ahead_count, behind: behind_count, status: "ahead" }
|
|
43
54
|
elsif behind_count.positive?
|
|
44
55
|
puts_verbose "main_vs_remote_main_ahead: #{ahead_count}"
|
|
45
56
|
puts_verbose "main_vs_remote_main_behind: #{behind_count}"
|
|
46
57
|
puts_verbose "ACTION: local #{config.main_branch} is behind #{config.git_remote}/#{config.main_branch} by #{behind_count} commit#{plural_suffix( count: behind_count )}; run carson sync."
|
|
47
58
|
audit_state = "attention" if audit_state == "ok"
|
|
48
59
|
audit_concise_problems << "Main sync (#{config.git_remote}): behind by #{behind_count} — run carson sync."
|
|
60
|
+
main_sync = { ahead: ahead_count, behind: behind_count, status: "behind" }
|
|
49
61
|
else
|
|
50
62
|
puts_verbose "main_vs_remote_main_ahead: 0"
|
|
51
63
|
puts_verbose "main_vs_remote_main_behind: 0"
|
|
@@ -109,15 +121,40 @@ module Carson
|
|
|
109
121
|
audit_status: audit_state
|
|
110
122
|
)
|
|
111
123
|
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
exit_code = audit_state == "block" ? EXIT_BLOCK : EXIT_OK
|
|
125
|
+
|
|
126
|
+
if json_output
|
|
127
|
+
result = {
|
|
128
|
+
command: "audit",
|
|
129
|
+
status: audit_state,
|
|
130
|
+
branch: current_branch,
|
|
131
|
+
hooks: { status: hooks_status },
|
|
132
|
+
main_sync: main_sync,
|
|
133
|
+
pr: monitor_report[ :pr ],
|
|
134
|
+
checks: monitor_report.fetch( :checks ),
|
|
135
|
+
baseline: {
|
|
136
|
+
status: default_branch_baseline.fetch( :status ),
|
|
137
|
+
repository: default_branch_baseline[ :repository ],
|
|
138
|
+
failing_count: default_branch_baseline.fetch( :failing_count ),
|
|
139
|
+
pending_count: default_branch_baseline.fetch( :pending_count ),
|
|
140
|
+
advisory_failing_count: default_branch_baseline.fetch( :advisory_failing_count ),
|
|
141
|
+
advisory_pending_count: default_branch_baseline.fetch( :advisory_pending_count )
|
|
142
|
+
},
|
|
143
|
+
problems: audit_concise_problems,
|
|
144
|
+
exit_code: exit_code
|
|
145
|
+
}
|
|
146
|
+
out.puts JSON.pretty_generate( result )
|
|
147
|
+
else
|
|
148
|
+
puts_verbose ""
|
|
149
|
+
puts_verbose "[Audit Result]"
|
|
150
|
+
puts_verbose "status: #{audit_state}"
|
|
151
|
+
puts_verbose( audit_state == "block" ? "ACTION: local policy block must be resolved before commit/push." : "ACTION: no local hard block detected." )
|
|
152
|
+
unless verbose?
|
|
153
|
+
audit_concise_problems.each { |problem| puts_line problem }
|
|
154
|
+
puts_line "Audit: #{audit_state}"
|
|
155
|
+
end
|
|
119
156
|
end
|
|
120
|
-
|
|
157
|
+
exit_code
|
|
121
158
|
end
|
|
122
159
|
|
|
123
160
|
private
|
|
@@ -2,73 +2,135 @@
|
|
|
2
2
|
# Collapses the 8-step manual PR flow into one or two commands.
|
|
3
3
|
# `carson deliver` pushes and creates the PR.
|
|
4
4
|
# `carson deliver --merge` also merges if CI is green.
|
|
5
|
+
# `carson deliver --json` outputs structured result for agent consumption.
|
|
5
6
|
module Carson
|
|
6
7
|
class Runtime
|
|
7
8
|
module Deliver
|
|
8
9
|
# Entry point for `carson deliver`.
|
|
9
10
|
# Pushes current branch, creates a PR if needed, reports the PR URL.
|
|
10
11
|
# With merge: true, also merges if CI passes and cleans up.
|
|
11
|
-
def deliver!( merge: false, title: nil, body_file: nil )
|
|
12
|
+
def deliver!( merge: false, title: nil, body_file: nil, json_output: false )
|
|
12
13
|
branch = current_branch
|
|
13
14
|
main = config.main_branch
|
|
14
15
|
remote = config.git_remote
|
|
16
|
+
result = { command: "deliver", branch: branch }
|
|
15
17
|
|
|
16
18
|
# Guard: cannot deliver from main.
|
|
17
19
|
if branch == main
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
result[ :error ] = "cannot deliver from #{main}"
|
|
21
|
+
result[ :recovery ] = "git checkout -b <branch-name>"
|
|
22
|
+
return deliver_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output )
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
# Step 1: push the branch.
|
|
23
|
-
|
|
24
|
-
return
|
|
26
|
+
push_exit = push_branch!( branch: branch, remote: remote, result: result )
|
|
27
|
+
return deliver_finish( result: result, exit_code: push_exit, json_output: json_output ) unless push_exit == EXIT_OK
|
|
25
28
|
|
|
26
29
|
# Step 2: find or create the PR.
|
|
27
30
|
pr_number, pr_url = find_or_create_pr!(
|
|
28
|
-
branch: branch, title: title, body_file: body_file
|
|
31
|
+
branch: branch, title: title, body_file: body_file, result: result
|
|
29
32
|
)
|
|
30
|
-
|
|
33
|
+
if pr_number.nil?
|
|
34
|
+
return deliver_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output )
|
|
35
|
+
end
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
result[ :pr_number ] = pr_number
|
|
38
|
+
result[ :pr_url ] = pr_url
|
|
33
39
|
|
|
34
40
|
# Without --merge, we are done.
|
|
35
|
-
|
|
41
|
+
unless merge
|
|
42
|
+
return deliver_finish( result: result, exit_code: EXIT_OK, json_output: json_output )
|
|
43
|
+
end
|
|
36
44
|
|
|
37
45
|
# Step 3: check CI status.
|
|
38
46
|
ci_status = check_pr_ci( number: pr_number )
|
|
47
|
+
result[ :ci ] = ci_status.to_s
|
|
48
|
+
|
|
39
49
|
case ci_status
|
|
40
50
|
when :pass
|
|
41
|
-
|
|
51
|
+
# Continue to merge.
|
|
42
52
|
when :pending
|
|
43
|
-
|
|
44
|
-
return EXIT_OK
|
|
53
|
+
result[ :recovery ] = "gh pr checks #{pr_number} --watch && carson deliver --merge"
|
|
54
|
+
return deliver_finish( result: result, exit_code: EXIT_OK, json_output: json_output )
|
|
45
55
|
when :fail
|
|
46
|
-
|
|
47
|
-
return EXIT_BLOCK
|
|
56
|
+
result[ :recovery ] = "gh pr checks #{pr_number} — fix failures, push, then `carson deliver --merge`"
|
|
57
|
+
return deliver_finish( result: result, exit_code: EXIT_BLOCK, json_output: json_output )
|
|
48
58
|
else
|
|
49
|
-
|
|
50
|
-
return EXIT_OK
|
|
59
|
+
result[ :recovery ] = "gh pr checks #{pr_number}"
|
|
60
|
+
return deliver_finish( result: result, exit_code: EXIT_OK, json_output: json_output )
|
|
51
61
|
end
|
|
52
62
|
|
|
53
63
|
# Step 4: merge.
|
|
54
|
-
|
|
55
|
-
return
|
|
64
|
+
merge_exit = merge_pr!( number: pr_number, result: result )
|
|
65
|
+
return deliver_finish( result: result, exit_code: merge_exit, json_output: json_output ) unless merge_exit == EXIT_OK
|
|
66
|
+
|
|
67
|
+
result[ :merged ] = true
|
|
56
68
|
|
|
57
69
|
# Step 5: sync main.
|
|
58
70
|
sync_after_merge!( remote: remote, main: main )
|
|
59
71
|
|
|
60
|
-
EXIT_OK
|
|
72
|
+
deliver_finish( result: result, exit_code: EXIT_OK, json_output: json_output )
|
|
61
73
|
end
|
|
62
74
|
|
|
63
75
|
private
|
|
64
76
|
|
|
77
|
+
# Outputs the final result — JSON or human-readable — and returns exit code.
|
|
78
|
+
def deliver_finish( result:, exit_code:, json_output: )
|
|
79
|
+
result[ :exit_code ] = exit_code
|
|
80
|
+
|
|
81
|
+
if json_output
|
|
82
|
+
out.puts JSON.pretty_generate( result )
|
|
83
|
+
else
|
|
84
|
+
print_deliver_human( result: result )
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
exit_code
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Human-readable output for deliver results.
|
|
91
|
+
def print_deliver_human( result: )
|
|
92
|
+
exit_code = result.fetch( :exit_code )
|
|
93
|
+
|
|
94
|
+
if result[ :error ]
|
|
95
|
+
puts_line "ERROR: #{result[ :error ]}"
|
|
96
|
+
puts_line " Recovery: #{result[ :recovery ]}" if result[ :recovery ]
|
|
97
|
+
return
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if result[ :pr_number ]
|
|
101
|
+
puts_line "PR: ##{result[ :pr_number ]} #{result[ :pr_url ]}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if result[ :ci ]
|
|
105
|
+
ci = result[ :ci ]
|
|
106
|
+
case ci
|
|
107
|
+
when "pass"
|
|
108
|
+
puts_line "CI: pass"
|
|
109
|
+
when "pending"
|
|
110
|
+
puts_line "CI: pending — merge when checks complete."
|
|
111
|
+
puts_line " Recovery: #{result[ :recovery ]}" if result[ :recovery ]
|
|
112
|
+
when "fail"
|
|
113
|
+
puts_line "CI: failing — fix before merging."
|
|
114
|
+
puts_line " Recovery: #{result[ :recovery ]}" if result[ :recovery ]
|
|
115
|
+
else
|
|
116
|
+
puts_line "CI: #{ci} — check manually."
|
|
117
|
+
puts_line " Recovery: #{result[ :recovery ]}" if result[ :recovery ]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if result[ :merged ]
|
|
122
|
+
puts_line "Merged PR ##{result[ :pr_number ]} via #{result[ :merge_method ]}."
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
65
126
|
# Pushes the branch to the remote with tracking.
|
|
66
|
-
def push_branch!( branch:, remote: )
|
|
127
|
+
def push_branch!( branch:, remote:, result: )
|
|
67
128
|
_, push_stderr, push_success, = git_run( "push", "-u", remote, branch )
|
|
68
129
|
unless push_success
|
|
69
130
|
error_text = push_stderr.to_s.strip
|
|
70
131
|
error_text = "push failed" if error_text.empty?
|
|
71
|
-
|
|
132
|
+
result[ :error ] = error_text
|
|
133
|
+
result[ :recovery ] = "git pull #{remote} #{branch} --rebase && git push -u #{remote} #{branch}"
|
|
72
134
|
return EXIT_ERROR
|
|
73
135
|
end
|
|
74
136
|
puts_verbose "pushed #{branch} to #{remote}"
|
|
@@ -77,13 +139,13 @@ module Carson
|
|
|
77
139
|
|
|
78
140
|
# Finds an existing PR for the branch, or creates a new one.
|
|
79
141
|
# Returns [number, url] or [nil, nil] on failure.
|
|
80
|
-
def find_or_create_pr!( branch:, title: nil, body_file: nil )
|
|
142
|
+
def find_or_create_pr!( branch:, title: nil, body_file: nil, result: )
|
|
81
143
|
# Check for existing PR.
|
|
82
144
|
existing = find_existing_pr( branch: branch )
|
|
83
145
|
return existing if existing.first
|
|
84
146
|
|
|
85
147
|
# Create a new PR.
|
|
86
|
-
create_pr!( branch: branch, title: title, body_file: body_file )
|
|
148
|
+
create_pr!( branch: branch, title: title, body_file: body_file, result: result )
|
|
87
149
|
end
|
|
88
150
|
|
|
89
151
|
# Queries gh for an open PR on this branch.
|
|
@@ -104,7 +166,7 @@ module Carson
|
|
|
104
166
|
|
|
105
167
|
# Creates a PR via gh. Title defaults to branch name humanised.
|
|
106
168
|
# Returns [number, url] or [nil, nil] on failure.
|
|
107
|
-
def create_pr!( branch:, title: nil, body_file: nil )
|
|
169
|
+
def create_pr!( branch:, title: nil, body_file: nil, result: )
|
|
108
170
|
pr_title = title || default_pr_title( branch: branch )
|
|
109
171
|
|
|
110
172
|
args = [ "pr", "create", "--title", pr_title, "--head", branch ]
|
|
@@ -118,7 +180,8 @@ module Carson
|
|
|
118
180
|
unless success
|
|
119
181
|
error_text = stderr.to_s.strip
|
|
120
182
|
error_text = "pr create failed" if error_text.empty?
|
|
121
|
-
|
|
183
|
+
result[ :error ] = error_text
|
|
184
|
+
result[ :recovery ] = "gh pr create --title '#{pr_title}' --head #{branch}"
|
|
122
185
|
return [ nil, nil ]
|
|
123
186
|
end
|
|
124
187
|
|
|
@@ -160,21 +223,23 @@ module Carson
|
|
|
160
223
|
end
|
|
161
224
|
|
|
162
225
|
# Merges the PR using the configured merge method.
|
|
163
|
-
def merge_pr!( number: )
|
|
226
|
+
def merge_pr!( number:, result: )
|
|
164
227
|
method = config.govern_merge_method
|
|
165
|
-
|
|
228
|
+
result[ :merge_method ] = method
|
|
229
|
+
|
|
230
|
+
_, stderr, success, = gh_run(
|
|
166
231
|
"pr", "merge", number.to_s,
|
|
167
232
|
"--#{method}",
|
|
168
233
|
"--delete-branch"
|
|
169
234
|
)
|
|
170
235
|
|
|
171
236
|
if success
|
|
172
|
-
puts_line "Merged PR ##{number} via #{method}."
|
|
173
237
|
EXIT_OK
|
|
174
238
|
else
|
|
175
239
|
error_text = stderr.to_s.strip
|
|
176
240
|
error_text = "merge failed" if error_text.empty?
|
|
177
|
-
|
|
241
|
+
result[ :error ] = error_text
|
|
242
|
+
result[ :recovery ] = "gh pr merge #{number} --#{method} --delete-branch"
|
|
178
243
|
EXIT_ERROR
|
|
179
244
|
end
|
|
180
245
|
end
|