reviewer 1.1.1 → 1.2.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/CHANGELOG.md +22 -0
- data/docs/usage.md +13 -4
- data/lib/reviewer/arguments/files/branched_files.rb +92 -0
- data/lib/reviewer/arguments/files.rb +20 -3
- data/lib/reviewer/arguments/keywords.rb +1 -1
- data/lib/reviewer/capabilities.rb +4 -1
- data/lib/reviewer/session/formatter.rb +36 -4
- data/lib/reviewer/session.rb +12 -10
- data/lib/reviewer/shell/result.rb +11 -2
- data/lib/reviewer/version.rb +1 -1
- data/lib/reviewer.rb +17 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e521c12a7d3f686337af0ad6dff4adb7e1c0f6e7ec49e05ed244931bf27d14f
|
|
4
|
+
data.tar.gz: 779c50bbe81345a91f80d31b5b9c269d283fc8e3b0d54f5193e8e01e4ca9b0d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 764931418180d96bc1331f43a67c333b1788169f41584a3603684e1fbd1f801a00a72fc9a578a4052535327256281de299cb90d86419f1438ac4e7edc87bdab0
|
|
7
|
+
data.tar.gz: 4045484668eab50715cb5b1d61961db4ea9377a12c5e5bb1bd097425c7413cde923dc17cc6a1351b11d6f63336ebf1e33bdbbe2d6ccda0d130e447937d179a52
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `branched` keyword: selects every file that differs from `origin/HEAD` since the current work split
|
|
8
|
+
from it, including uncommitted and new files, so a whole branch can be reviewed before a pull
|
|
9
|
+
request. It never substitutes another branch: when it can't compare against `origin/HEAD`, it
|
|
10
|
+
stops with a usage error that names the fix or shows Git's message.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Report tool failures normally when a tool prints non-ASCII text under a non-UTF-8 locale (such as
|
|
15
|
+
`LC_ALL=C` or an unrecognized `LC_ALL`), or prints bytes that aren't valid UTF-8, instead of crashing
|
|
16
|
+
with an encoding error. Captured output is read as UTF-8 and invalid bytes are replaced.
|
|
17
|
+
- Include files with non-ASCII names in `staged`, `unstaged`, `modified` and `untracked` runs. Git's
|
|
18
|
+
escaped path names previously never matched the real files, so those files were silently skipped,
|
|
19
|
+
and a non-UTF-8 locale could crash the run.
|
|
20
|
+
- Report a mistyped or unsupported option, such as `rvw --list`, as a usage error with exit status 2
|
|
21
|
+
instead of crashing with a backtrace.
|
|
22
|
+
- Describe `modified` accurately in `--capabilities` and `--help`: it covers tracked files changed
|
|
23
|
+
since the last commit and excludes untracked files.
|
|
24
|
+
|
|
3
25
|
## [1.1.1] - 2026-09-02
|
|
4
26
|
|
|
5
27
|
### Fixed
|
data/docs/usage.md
CHANGED
|
@@ -59,8 +59,16 @@ File selectors resolve paths from Git:
|
|
|
59
59
|
|---|---|
|
|
60
60
|
| `staged` | Staged changes |
|
|
61
61
|
| `unstaged` | Unstaged changes |
|
|
62
|
-
| `modified` |
|
|
62
|
+
| `modified` | Tracked files changed since the last commit (staged and unstaged); excludes untracked files |
|
|
63
63
|
| `untracked` | Untracked, non-ignored files |
|
|
64
|
+
| `branched` | Every file that differs from `origin/HEAD` since this work split from it, including uncommitted and new files |
|
|
65
|
+
|
|
66
|
+
`branched` always compares against `origin/HEAD` and never substitutes another branch. When it
|
|
67
|
+
can't, it stops with a usage error rather than reviewing nothing:
|
|
68
|
+
|
|
69
|
+
- If `origin/HEAD` isn't set, run `git remote set-head origin --auto`.
|
|
70
|
+
- In a shallow clone, such as a default CI checkout, fetch the default branch with its history
|
|
71
|
+
(for example, `fetch-depth: 0`).
|
|
64
72
|
|
|
65
73
|
`failed` selects tools whose last executed review failed and, when available, reuses each tool's
|
|
66
74
|
stored failed file paths. File-aware tools retry those paths; tools without file support retry their
|
|
@@ -73,12 +81,13 @@ Selections compose:
|
|
|
73
81
|
```console
|
|
74
82
|
rvw rubocop staged
|
|
75
83
|
rvw -t ruby modified
|
|
84
|
+
rvw rubocop branched
|
|
76
85
|
rvw tests -f test/reviewer_test.rb
|
|
77
86
|
```
|
|
78
87
|
|
|
79
|
-
Explicit `-f`, `staged`, `unstaged`, `modified`, and `
|
|
80
|
-
`files:` configuration instead of running their full-project command. Bare `rvw` remains
|
|
81
|
-
configured review.
|
|
88
|
+
Explicit `-f`, `staged`, `unstaged`, `modified`, `untracked`, and `branched` requests skip tools
|
|
89
|
+
without a `files:` configuration instead of running their full-project command. Bare `rvw` remains
|
|
90
|
+
the full configured review.
|
|
82
91
|
|
|
83
92
|
The [configuration reference](configuration.md#file-targeting) describes filtering, file-scoped
|
|
84
93
|
commands, and Minitest/RSpec source-to-test mapping.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Reviewer
|
|
6
|
+
class Arguments
|
|
7
|
+
class Files
|
|
8
|
+
# Selects the files for the `branched` keyword: every file that differs from origin/HEAD since
|
|
9
|
+
# this work split from it, including uncommitted and new files. It compares against origin/HEAD
|
|
10
|
+
# only, so when any git step fails, `missing` says why and no files are selected rather than
|
|
11
|
+
# substituting another branch or reviewing nothing.
|
|
12
|
+
class BranchedFiles
|
|
13
|
+
# The problem, then how to fix it. An unexpected git error has no known fix, so git's own
|
|
14
|
+
# message is shown instead.
|
|
15
|
+
MISSING_MESSAGES = {
|
|
16
|
+
origin_head: [
|
|
17
|
+
"Can't compare against origin/HEAD: origin/HEAD isn't set",
|
|
18
|
+
'Run `git remote set-head origin --auto`, then try again.'
|
|
19
|
+
],
|
|
20
|
+
merge_base: [
|
|
21
|
+
"Can't find where this work split from origin/HEAD",
|
|
22
|
+
'Fetch the default branch with its history (for example, `fetch-depth: 0` in CI), then try again.'
|
|
23
|
+
],
|
|
24
|
+
git_error: ["Git couldn't list the files that differ from origin/HEAD"]
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
# Why the files couldn't be selected. `reason` is :origin_head when origin/HEAD isn't set,
|
|
28
|
+
# :merge_base when HEAD shares no history with it (as in a shallow clone), or :git_error for
|
|
29
|
+
# any other git failure, whose message is kept in `detail`.
|
|
30
|
+
Missing = Struct.new(:reason, :detail) do
|
|
31
|
+
# @return [String] what went wrong
|
|
32
|
+
def problem = MISSING_MESSAGES.fetch(reason).first
|
|
33
|
+
|
|
34
|
+
# @return [String, nil] how to fix it, or git's message for an unexpected error
|
|
35
|
+
def fix = MISSING_MESSAGES.fetch(reason)[1] || detail
|
|
36
|
+
|
|
37
|
+
# @return [String] the problem and its fix as one line
|
|
38
|
+
def message = "#{problem}. #{fix}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @!attribute [r] files
|
|
42
|
+
# @return [Array<String>] the selected paths, empty when `missing` is set
|
|
43
|
+
# @!attribute [r] missing
|
|
44
|
+
# @return [Missing, nil] why the files couldn't be selected, or nil when they were
|
|
45
|
+
attr_reader :files, :missing
|
|
46
|
+
|
|
47
|
+
# Runs every git step immediately so both attributes reflect the same git state
|
|
48
|
+
#
|
|
49
|
+
# @return [self]
|
|
50
|
+
def initialize
|
|
51
|
+
@missing = nil
|
|
52
|
+
@files = select || []
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def select
|
|
58
|
+
merge_base = find_merge_base
|
|
59
|
+
changed = merge_base && lookup(%W[diff --name-only #{merge_base}])
|
|
60
|
+
untracked = changed && lookup(%w[ls-files --others --exclude-standard])
|
|
61
|
+
return unless untracked
|
|
62
|
+
|
|
63
|
+
(changed.split("\n") + untracked.split("\n")).reject(&:empty?)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def find_merge_base
|
|
67
|
+
lookup(%w[rev-parse --verify --quiet refs/remotes/origin/HEAD], when_absent: :origin_head) &&
|
|
68
|
+
lookup(%w[merge-base HEAD origin/HEAD], when_absent: :merge_base)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Git exits 1 when a ref or merge base doesn't exist. Any other failure is reported with git's
|
|
72
|
+
# own message, since the fix for an absent ref wouldn't apply to it. Output is read as UTF-8
|
|
73
|
+
# with paths unescaped, like the other git keywords.
|
|
74
|
+
def lookup(options, when_absent: :git_error)
|
|
75
|
+
stdout, stderr, status = Open3.capture3('git', '-c', 'core.quotePath=false', '--no-pager', *options)
|
|
76
|
+
case status.exitstatus
|
|
77
|
+
when 0 then Shell::Result.decode(stdout).strip
|
|
78
|
+
when 1 then record_missing(when_absent, stderr)
|
|
79
|
+
else record_missing(:git_error, stderr)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Only an unexpected error keeps git's message; an absent ref or merge base has a known fix
|
|
84
|
+
def record_missing(reason, stderr)
|
|
85
|
+
detail = (Shell::Result.decode(stderr).lines.first&.strip if reason == :git_error)
|
|
86
|
+
@missing = Missing.new(reason, detail)
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'open3'
|
|
4
4
|
|
|
5
|
+
require_relative 'files/branched_files'
|
|
6
|
+
|
|
5
7
|
module Reviewer
|
|
6
8
|
class Arguments
|
|
7
9
|
# Generates a Ruby-friendly list (Array) of files to run the command against from the provided
|
|
@@ -56,6 +58,15 @@ module Reviewer
|
|
|
56
58
|
end
|
|
57
59
|
alias inspect to_h
|
|
58
60
|
|
|
61
|
+
# Why `branched` couldn't select files. `branched` never substitutes another ref, so the
|
|
62
|
+
# caller reports this instead of reviewing nothing.
|
|
63
|
+
#
|
|
64
|
+
# @return [BranchedFiles::Missing, nil] why the files couldn't be selected, or nil when they
|
|
65
|
+
# were or `branched` wasn't requested
|
|
66
|
+
def missing_base
|
|
67
|
+
branched_files.missing if keywords.include?('branched')
|
|
68
|
+
end
|
|
69
|
+
|
|
59
70
|
private
|
|
60
71
|
|
|
61
72
|
# Combines the sorted list of unique files/paths by merging the explicitly-provided file
|
|
@@ -101,15 +112,21 @@ module Reviewer
|
|
|
101
112
|
git_files(%w[ls-files --others --exclude-standard])
|
|
102
113
|
end
|
|
103
114
|
|
|
104
|
-
|
|
115
|
+
def branched = branched_files.files
|
|
116
|
+
|
|
117
|
+
def branched_files = @branched_files ||= BranchedFiles.new
|
|
118
|
+
|
|
119
|
+
# Executes a git command and returns the output as an array of file paths. Git escapes
|
|
120
|
+
# non-ASCII paths unless `core.quotePath` is off, and its output is read as UTF-8 like any
|
|
121
|
+
# other command output so the paths match the real files regardless of locale.
|
|
105
122
|
# @param options [Array<String>] the git command options
|
|
106
123
|
#
|
|
107
124
|
# @return [Array<String>] the output lines from the command
|
|
108
125
|
def git_files(options)
|
|
109
|
-
command = (%w[git --no-pager] + options).join(' ')
|
|
126
|
+
command = (%w[git -c core.quotePath=false --no-pager] + options).join(' ')
|
|
110
127
|
stdout, stderr, status = Open3.capture3(command)
|
|
111
128
|
|
|
112
|
-
return stdout.split("\n").reject(&:empty?) if status.success?
|
|
129
|
+
return Shell::Result.decode(stdout).split("\n").reject(&:empty?) if status.success?
|
|
113
130
|
|
|
114
131
|
raise SystemCallError.new("Git Error: #{stderr} (#{command})", status.exitstatus.to_i)
|
|
115
132
|
rescue SystemCallError => e
|
|
@@ -10,7 +10,7 @@ module Reviewer
|
|
|
10
10
|
class Keywords
|
|
11
11
|
# Keywords that resolve to a list of files. `Arguments::Files` implements one method per
|
|
12
12
|
# entry, so a keyword here without a matching method silently contributes nothing.
|
|
13
|
-
FOR_FILES = %w[staged unstaged modified untracked].freeze
|
|
13
|
+
FOR_FILES = %w[staged unstaged modified untracked branched].freeze
|
|
14
14
|
|
|
15
15
|
# Keywords that select tools rather than files. `failed` reads `last_status` from history via
|
|
16
16
|
# `Tools#failed_from_history`; the per-tool file scope comes from `Command#stored_failed_files`.
|
|
@@ -26,8 +26,10 @@ module Reviewer
|
|
|
26
26
|
KEYWORD_DESCRIPTIONS = {
|
|
27
27
|
'staged' => 'Files staged for commit',
|
|
28
28
|
'unstaged' => 'Files with unstaged changes',
|
|
29
|
-
'modified' => '
|
|
29
|
+
'modified' => 'Tracked files changed since the last commit (staged and unstaged); excludes untracked files',
|
|
30
30
|
'untracked' => 'New files not yet tracked',
|
|
31
|
+
'branched' => 'Every file that differs from origin/HEAD since this work split from it, ' \
|
|
32
|
+
'including uncommitted and new files',
|
|
31
33
|
'failed' => 'Tools whose last executed review failed and have not passed since'
|
|
32
34
|
}.freeze
|
|
33
35
|
|
|
@@ -38,6 +40,7 @@ module Reviewer
|
|
|
38
40
|
SCENARIOS = {
|
|
39
41
|
before_commit: 'rvw staged',
|
|
40
42
|
during_development: 'rvw modified',
|
|
43
|
+
before_pull_request: 'rvw branched',
|
|
41
44
|
full_review: 'rvw'
|
|
42
45
|
}.freeze
|
|
43
46
|
|
|
@@ -10,6 +10,8 @@ module Reviewer
|
|
|
10
10
|
|
|
11
11
|
MISSING_FILES_MESSAGE = 'The --files option requires at least one file or path.'
|
|
12
12
|
|
|
13
|
+
BRANCHED_HINT = "'branched' always compares against origin/HEAD and doesn't take a branch name"
|
|
14
|
+
|
|
13
15
|
attr_reader :output, :printer
|
|
14
16
|
private :output, :printer
|
|
15
17
|
|
|
@@ -25,31 +27,35 @@ module Reviewer
|
|
|
25
27
|
# Displays warnings for keywords that don't match any tool or git scope
|
|
26
28
|
# @param unrecognized [Array<String>] the unrecognized keyword strings
|
|
27
29
|
# @param suggestions [Hash{String => String}] keyword => suggested correction
|
|
30
|
+
# @param hint [String, nil] a closing note about how the request was misread
|
|
28
31
|
#
|
|
29
32
|
# @return [void]
|
|
30
|
-
def unrecognized_keywords(unrecognized, suggestions)
|
|
33
|
+
def unrecognized_keywords(unrecognized, suggestions, hint = nil)
|
|
31
34
|
unrecognized.each do |keyword|
|
|
32
35
|
printer.puts(:warning, "Unrecognized: #{keyword}")
|
|
33
36
|
suggestion = suggestions[keyword]
|
|
34
37
|
printer.puts(:muted, " did you mean '#{suggestion}'?") if suggestion
|
|
35
38
|
end
|
|
39
|
+
printer.puts(:muted, hint) if hint
|
|
36
40
|
output.newline
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
# Renders a machine-readable envelope for a name Reviewer cannot honour
|
|
40
44
|
# @param unrecognized [Array<String>] the names that matched nothing
|
|
41
45
|
# @param suggestions [Hash] closest known name per unrecognized name
|
|
46
|
+
# @param hint [String, nil] a note about how the request was misread, omitted when nil
|
|
42
47
|
#
|
|
43
48
|
# @return [void]
|
|
44
|
-
def unrecognized_keywords_json(unrecognized, suggestions)
|
|
49
|
+
def unrecognized_keywords_json(unrecognized, suggestions, hint = nil)
|
|
45
50
|
payload = {
|
|
46
51
|
schema_version: Report::SCHEMA_VERSION,
|
|
47
52
|
state: 'error',
|
|
48
53
|
error: {
|
|
49
54
|
code: 'unrecognized_selector',
|
|
50
55
|
message: "Unrecognized: #{unrecognized.join(', ')}",
|
|
51
|
-
suggestions: suggestions
|
|
52
|
-
|
|
56
|
+
suggestions: suggestions,
|
|
57
|
+
hint: hint
|
|
58
|
+
}.compact,
|
|
53
59
|
summary: Report.empty_summary,
|
|
54
60
|
tools: []
|
|
55
61
|
}
|
|
@@ -80,6 +86,32 @@ module Reviewer
|
|
|
80
86
|
printer.write_raw("#{JSON.pretty_generate(payload)}\n")
|
|
81
87
|
end
|
|
82
88
|
|
|
89
|
+
# Displays a usage error when `branched` can't find where the work split from origin/HEAD
|
|
90
|
+
# @param missing [Arguments::Files::BranchedFiles::Missing] why the branched files couldn't be selected
|
|
91
|
+
#
|
|
92
|
+
# @return [void]
|
|
93
|
+
def missing_base(missing)
|
|
94
|
+
printer.puts(:warning, missing.problem)
|
|
95
|
+
printer.puts(:muted, missing.fix)
|
|
96
|
+
output.newline
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Renders the machine-readable form of the missing base usage error
|
|
100
|
+
# @param missing [Arguments::Files::BranchedFiles::Missing] why the branched files couldn't be selected
|
|
101
|
+
#
|
|
102
|
+
# @return [void]
|
|
103
|
+
def missing_base_json(missing)
|
|
104
|
+
payload = {
|
|
105
|
+
schema_version: Report::SCHEMA_VERSION,
|
|
106
|
+
state: 'error',
|
|
107
|
+
error: { code: 'missing_base', message: missing.message },
|
|
108
|
+
summary: Report.empty_summary,
|
|
109
|
+
tools: []
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
printer.write_raw("#{JSON.pretty_generate(payload)}\n")
|
|
113
|
+
end
|
|
114
|
+
|
|
83
115
|
# Displays a warning when an unrecognized output format is requested
|
|
84
116
|
# @param value [String] the invalid format name
|
|
85
117
|
# @param known [Array<Symbol>] the valid format options
|
data/lib/reviewer/session.rb
CHANGED
|
@@ -47,12 +47,18 @@ module Reviewer
|
|
|
47
47
|
def run_tools(command_type)
|
|
48
48
|
return reject_invalid_files_option if arguments.invalid_files_option?
|
|
49
49
|
return reject_unrecognized_selectors if unrecognized_selectors.any?
|
|
50
|
+
return reject_missing_base if arguments.files.missing_base
|
|
50
51
|
|
|
51
52
|
json_output? ? run_json(command_type) : run_text(command_type)
|
|
52
53
|
end
|
|
53
54
|
|
|
54
|
-
def reject_invalid_files_option
|
|
55
|
-
|
|
55
|
+
def reject_invalid_files_option = reject_as_usage_error(:missing_files_option)
|
|
56
|
+
|
|
57
|
+
def reject_missing_base = reject_as_usage_error(:missing_base, arguments.files.missing_base)
|
|
58
|
+
|
|
59
|
+
# Renders a usage error through the formatter's text or JSON form, then returns its status
|
|
60
|
+
def reject_as_usage_error(formatter_method, *)
|
|
61
|
+
formatter.public_send(json_output? ? :"#{formatter_method}_json" : formatter_method, *)
|
|
56
62
|
USAGE_ERROR
|
|
57
63
|
end
|
|
58
64
|
|
|
@@ -92,15 +98,11 @@ module Reviewer
|
|
|
92
98
|
# attributing that success to a tool that never ran.
|
|
93
99
|
def reject_unrecognized_selectors
|
|
94
100
|
names = unrecognized_selectors
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if json_output?
|
|
98
|
-
formatter.unrecognized_keywords_json(names, suggestions)
|
|
99
|
-
else
|
|
100
|
-
formatter.unrecognized_keywords(names, suggestions)
|
|
101
|
-
end
|
|
101
|
+
reject_as_usage_error(:unrecognized_keywords, names, build_suggestions(names), unrecognized_selector_hint)
|
|
102
|
+
end
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
def unrecognized_selector_hint
|
|
105
|
+
Session::Formatter::BRANCHED_HINT if arguments.files.keywords.include?('branched')
|
|
104
106
|
end
|
|
105
107
|
|
|
106
108
|
# Positional selectors and `-t` values are the same request spelled two ways,
|
|
@@ -42,12 +42,21 @@ module Reviewer
|
|
|
42
42
|
#
|
|
43
43
|
# @return [self]
|
|
44
44
|
def initialize(stdout = nil, stderr = nil, status = nil)
|
|
45
|
-
@stdout = stdout
|
|
46
|
-
@stderr = stderr
|
|
45
|
+
@stdout = self.class.decode(stdout)
|
|
46
|
+
@stderr = self.class.decode(stderr)
|
|
47
47
|
@status = status
|
|
48
48
|
@exit_status = status&.exitstatus
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
# Ruby labels command output with the locale's encoding, which is US-ASCII under `LC_ALL=C`
|
|
52
|
+
# or an unrecognized locale. Tools overwhelmingly emit UTF-8, so output is read as UTF-8
|
|
53
|
+
# regardless of locale, and bytes that aren't valid UTF-8 are replaced so that later string
|
|
54
|
+
# operations can't raise. Passthrough output reaches the terminal before this, unchanged.
|
|
55
|
+
# @param text [String, nil] raw output from a command
|
|
56
|
+
#
|
|
57
|
+
# @return [String, nil] valid UTF-8 text
|
|
58
|
+
def self.decode(text) = text && String.new(text, encoding: Encoding::UTF_8).scrub
|
|
59
|
+
|
|
51
60
|
def exists? = [stdout, stderr, exit_status].compact.any?
|
|
52
61
|
|
|
53
62
|
# Determines if re-running a command is entirely futile. Primarily to help when a command
|
data/lib/reviewer/version.rb
CHANGED
data/lib/reviewer.rb
CHANGED
|
@@ -41,6 +41,8 @@ module Reviewer
|
|
|
41
41
|
# @return [void] Prints output to the console
|
|
42
42
|
def review
|
|
43
43
|
handle_early_exits { exit build_session.review }
|
|
44
|
+
rescue Slop::Error => e
|
|
45
|
+
reject_invalid_option(e.message)
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
# Runs the `format` command for the specified tools/files for which it is configured.
|
|
@@ -48,6 +50,8 @@ module Reviewer
|
|
|
48
50
|
# @return [void] Prints output to the console
|
|
49
51
|
def format
|
|
50
52
|
handle_early_exits { exit build_session.format }
|
|
53
|
+
rescue Slop::Error => e
|
|
54
|
+
reject_invalid_option(e.message)
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
# The collection of arguments that were passed via the command line.
|
|
@@ -105,6 +109,16 @@ module Reviewer
|
|
|
105
109
|
yield
|
|
106
110
|
end
|
|
107
111
|
|
|
112
|
+
# A mistyped or unsupported option is a usage error, not a crash
|
|
113
|
+
# @param message [String] the parser's description of the invalid option
|
|
114
|
+
#
|
|
115
|
+
# @return [void]
|
|
116
|
+
def reject_invalid_option(message)
|
|
117
|
+
output.printer.puts(:warning, message)
|
|
118
|
+
output.printer.puts(:muted, 'Run `rvw --help` for usage.')
|
|
119
|
+
exit Session::USAGE_ERROR
|
|
120
|
+
end
|
|
121
|
+
|
|
108
122
|
def first_time_setup? = !arguments.invalid_files_option? && !configuration.file.exist?
|
|
109
123
|
|
|
110
124
|
def subcommand?(name) = ARGV.first == name.to_s
|
|
@@ -133,8 +147,9 @@ module Reviewer
|
|
|
133
147
|
Keywords:
|
|
134
148
|
staged Files staged for commit
|
|
135
149
|
unstaged Files with unstaged changes
|
|
136
|
-
modified
|
|
150
|
+
modified Tracked files changed since the last commit
|
|
137
151
|
untracked New files not yet tracked by git
|
|
152
|
+
branched All files that differ from origin/HEAD, including new ones
|
|
138
153
|
failed Re-run tools whose last executed review failed
|
|
139
154
|
|
|
140
155
|
Options:
|
|
@@ -144,6 +159,7 @@ module Reviewer
|
|
|
144
159
|
rvw rubocop Run RuboCop only
|
|
145
160
|
rvw staged Review staged files across all tools
|
|
146
161
|
rvw -t security modified Run security-tagged tools on changed files
|
|
162
|
+
rvw branched Review everything this branch changes before a PR
|
|
147
163
|
rvw tests -f test/user_test.rb Run tests on a specific file
|
|
148
164
|
rvw failed Re-run tools whose last executed review failed
|
|
149
165
|
fmt rubocop Auto-fix with RuboCop
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: reviewer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Garrett Dimon
|
|
@@ -190,6 +190,7 @@ files:
|
|
|
190
190
|
- lib/reviewer.rb
|
|
191
191
|
- lib/reviewer/arguments.rb
|
|
192
192
|
- lib/reviewer/arguments/files.rb
|
|
193
|
+
- lib/reviewer/arguments/files/branched_files.rb
|
|
193
194
|
- lib/reviewer/arguments/keywords.rb
|
|
194
195
|
- lib/reviewer/arguments/options.rb
|
|
195
196
|
- lib/reviewer/arguments/tags.rb
|