pronto 0.9.0 → 0.11.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 +5 -5
- data/.github/CODEOWNERS +3 -0
- data/CHANGELOG.md +144 -56
- data/CONTRIBUTING.md +1 -1
- data/LICENSE +1 -1
- data/README.md +140 -28
- data/lib/pronto.rb +2 -0
- data/lib/pronto/bitbucket.rb +7 -0
- data/lib/pronto/cli.rb +10 -5
- data/lib/pronto/clients/bitbucket_client.rb +52 -13
- data/lib/pronto/clients/bitbucket_server_client.rb +1 -1
- data/lib/pronto/config.rb +17 -1
- data/lib/pronto/config_file.rb +7 -3
- data/lib/pronto/formatter/bitbucket_pull_request_formatter.rb +10 -0
- data/lib/pronto/formatter/checkstyle_formatter.rb +1 -1
- data/lib/pronto/formatter/formatter.rb +2 -0
- data/lib/pronto/formatter/git_formatter.rb +2 -0
- data/lib/pronto/formatter/github_combined_status_formatter.rb +24 -0
- data/lib/pronto/formatter/github_pull_request_review_formatter.rb +1 -1
- data/lib/pronto/formatter/gitlab_merge_request_review_formatter.rb +29 -0
- data/lib/pronto/formatter/json_formatter.rb +1 -1
- data/lib/pronto/formatter/null_formatter.rb +1 -1
- data/lib/pronto/formatter/text_formatter.rb +1 -1
- data/lib/pronto/formatter/text_message_decorator.rb +3 -0
- data/lib/pronto/git/patch.rb +0 -2
- data/lib/pronto/git/repository.rb +15 -4
- data/lib/pronto/github.rb +35 -13
- data/lib/pronto/github_pull.rb +43 -0
- data/lib/pronto/gitlab.rb +58 -1
- data/lib/pronto/runner.rb +8 -1
- data/lib/pronto/version.rb +1 -1
- data/pronto.gemspec +11 -11
- metadata +89 -80
data/lib/pronto.rb
CHANGED
@@ -42,9 +42,11 @@ require 'pronto/formatter/commit_formatter'
|
|
42
42
|
require 'pronto/formatter/pull_request_formatter'
|
43
43
|
require 'pronto/formatter/github_formatter'
|
44
44
|
require 'pronto/formatter/github_status_formatter'
|
45
|
+
require 'pronto/formatter/github_combined_status_formatter'
|
45
46
|
require 'pronto/formatter/github_pull_request_formatter'
|
46
47
|
require 'pronto/formatter/github_pull_request_review_formatter'
|
47
48
|
require 'pronto/formatter/gitlab_formatter'
|
49
|
+
require 'pronto/formatter/gitlab_merge_request_review_formatter'
|
48
50
|
require 'pronto/formatter/bitbucket_formatter'
|
49
51
|
require 'pronto/formatter/bitbucket_pull_request_formatter'
|
50
52
|
require 'pronto/formatter/bitbucket_server_pull_request_formatter'
|
data/lib/pronto/bitbucket.rb
CHANGED
data/lib/pronto/cli.rb
CHANGED
@@ -12,7 +12,7 @@ module Pronto
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
desc 'run', 'Run Pronto'
|
15
|
+
desc 'run [PATH]', 'Run Pronto'
|
16
16
|
|
17
17
|
method_option :'exit-code',
|
18
18
|
type: :boolean,
|
@@ -45,7 +45,9 @@ module Pronto
|
|
45
45
|
aliases: ['formatter', '-f'],
|
46
46
|
desc: "Pick output formatters. Available: #{::Pronto::Formatter.names.join(', ')}"
|
47
47
|
|
48
|
-
def run(path =
|
48
|
+
def run(path = '.')
|
49
|
+
path = File.expand_path(path)
|
50
|
+
|
49
51
|
gem_names = options[:runner].any? ? options[:runner] : ::Pronto::GemNames.new.to_a
|
50
52
|
gem_names.each do |gem_name|
|
51
53
|
require "pronto/#{gem_name}"
|
@@ -56,16 +58,19 @@ module Pronto
|
|
56
58
|
commit_options = %i[staged unstaged index]
|
57
59
|
commit = commit_options.find { |o| options[o] } || options[:commit]
|
58
60
|
|
59
|
-
repo_workdir = ::Rugged::Repository.discover(
|
61
|
+
repo_workdir = ::Rugged::Repository.discover(path).workdir
|
62
|
+
relative = path.sub(repo_workdir, '')
|
63
|
+
|
60
64
|
messages = Dir.chdir(repo_workdir) do
|
61
|
-
|
65
|
+
file = relative.length != path.length ? relative : nil
|
66
|
+
::Pronto.run(commit, '.', formatters, file)
|
62
67
|
end
|
63
68
|
if options[:'exit-code']
|
64
69
|
error_messages_count = messages.count { |m| m.level != :info }
|
65
70
|
exit(error_messages_count)
|
66
71
|
end
|
67
72
|
rescue Rugged::RepositoryError
|
68
|
-
puts '"pronto"
|
73
|
+
puts '"pronto" must be run from within a git repository or must be supplied the path to a git repository'
|
69
74
|
rescue Pronto::Error => e
|
70
75
|
$stderr.puts "Pronto errored: #{e.message}"
|
71
76
|
end
|
@@ -1,47 +1,86 @@
|
|
1
1
|
class BitbucketClient
|
2
2
|
include HTTParty
|
3
|
-
base_uri 'https://api.bitbucket.org/
|
3
|
+
base_uri 'https://api.bitbucket.org/2.0/repositories'
|
4
4
|
|
5
5
|
def initialize(username, password)
|
6
6
|
self.class.basic_auth(username, password)
|
7
7
|
end
|
8
8
|
|
9
9
|
def commit_comments(slug, sha)
|
10
|
-
response = get("/#{slug}/
|
11
|
-
openstruct(response)
|
10
|
+
response = get("/#{slug}/commit/#{sha}/comments?pagelen=100")
|
11
|
+
result = parse_comments(openstruct(response))
|
12
|
+
while (response['next'])
|
13
|
+
response = get response['next']
|
14
|
+
result.concat(parse_comments(openstruct(response)))
|
15
|
+
end
|
16
|
+
result
|
12
17
|
end
|
13
18
|
|
14
19
|
def create_commit_comment(slug, sha, body, path, position)
|
15
|
-
post("/#{slug}/
|
20
|
+
post("/#{slug}/commit/#{sha}/comments", body, path, position)
|
16
21
|
end
|
17
22
|
|
18
23
|
def pull_comments(slug, pull_id)
|
19
|
-
response = get("/#{slug}/pullrequests/#{pull_id}/comments")
|
20
|
-
openstruct(response)
|
24
|
+
response = get("/#{slug}/pullrequests/#{pull_id}/comments?pagelen=100")
|
25
|
+
parse_comments(openstruct(response))
|
26
|
+
result = parse_comments(openstruct(response))
|
27
|
+
while (response['next'])
|
28
|
+
response = get response['next']
|
29
|
+
result.concat(parse_comments(openstruct(response)))
|
30
|
+
end
|
31
|
+
result
|
21
32
|
end
|
22
33
|
|
23
34
|
def pull_requests(slug)
|
24
|
-
|
25
|
-
response
|
26
|
-
openstruct(response['values'])
|
35
|
+
response = get("/#{slug}/pullrequests?state=OPEN")
|
36
|
+
openstruct(response)
|
27
37
|
end
|
28
38
|
|
29
39
|
def create_pull_comment(slug, pull_id, body, path, position)
|
30
40
|
post("/#{slug}/pullrequests/#{pull_id}/comments", body, path, position)
|
31
41
|
end
|
32
42
|
|
43
|
+
def approve_pull_request(slug, pull_id)
|
44
|
+
self.class.post("/#{slug}/pullrequests/#{pull_id}/approve")
|
45
|
+
end
|
46
|
+
|
47
|
+
def unapprove_pull_request(slug, pull_id)
|
48
|
+
self.class.delete("/#{slug}/pullrequests/#{pull_id}/approve")
|
49
|
+
end
|
50
|
+
|
33
51
|
private
|
34
52
|
|
35
53
|
def openstruct(response)
|
36
|
-
response
|
54
|
+
if response['values']
|
55
|
+
response['values'].map { |r| OpenStruct.new(r) }
|
56
|
+
else
|
57
|
+
p response
|
58
|
+
raise 'BitBucket response invalid'
|
59
|
+
end
|
37
60
|
end
|
38
61
|
|
62
|
+
def parse_comments(values)
|
63
|
+
values.each do |value|
|
64
|
+
value.content = value.content['raw']
|
65
|
+
value.line_to = value.inline ? value.inline['to'] : 0
|
66
|
+
value.filename = value.inline ? value.inline['path'] : ''
|
67
|
+
end
|
68
|
+
values
|
69
|
+
end
|
70
|
+
|
39
71
|
def post(url, body, path, position)
|
40
72
|
options = {
|
41
73
|
body: {
|
42
|
-
content:
|
43
|
-
|
44
|
-
|
74
|
+
content: {
|
75
|
+
raw: body
|
76
|
+
},
|
77
|
+
inline: {
|
78
|
+
to: position,
|
79
|
+
path: path
|
80
|
+
}
|
81
|
+
}.to_json,
|
82
|
+
headers: {
|
83
|
+
'Content-Type': 'application/json'
|
45
84
|
}
|
46
85
|
}
|
47
86
|
self.class.post(url, options)
|
data/lib/pronto/config.rb
CHANGED
@@ -18,6 +18,18 @@ module Pronto
|
|
18
18
|
consolidated
|
19
19
|
end
|
20
20
|
|
21
|
+
def github_review_type
|
22
|
+
review_type =
|
23
|
+
ENV['PRONTO_GITHUB_REVIEW_TYPE'] ||
|
24
|
+
@config_hash.fetch('github_review_type', false)
|
25
|
+
|
26
|
+
if review_type == 'request_changes'
|
27
|
+
'REQUEST_CHANGES'
|
28
|
+
else
|
29
|
+
'COMMENT'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
21
33
|
def excluded_files(runner)
|
22
34
|
files =
|
23
35
|
if runner == 'all'
|
@@ -39,8 +51,12 @@ module Pronto
|
|
39
51
|
URI.parse(bitbucket_web_endpoint).host
|
40
52
|
end
|
41
53
|
|
54
|
+
def warnings_per_review
|
55
|
+
ENV['PRONTO_WARNINGS_PER_REVIEW'] && Integer(ENV['PRONTO_WARNINGS_PER_REVIEW']) || @config_hash['warnings_per_review']
|
56
|
+
end
|
57
|
+
|
42
58
|
def max_warnings
|
43
|
-
ENV['PRONTO_MAX_WARNINGS'] || @config_hash['max_warnings']
|
59
|
+
ENV['PRONTO_MAX_WARNINGS'] && Integer(ENV['PRONTO_MAX_WARNINGS']) || @config_hash['max_warnings']
|
44
60
|
end
|
45
61
|
|
46
62
|
def message_format(formatter)
|
data/lib/pronto/config_file.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Pronto
|
2
2
|
class ConfigFile
|
3
3
|
DEFAULT_MESSAGE_FORMAT = '%{msg}'.freeze
|
4
|
+
DEFAULT_WARNINGS_PER_REVIEW = 30
|
4
5
|
|
5
6
|
EMPTY = {
|
6
7
|
'all' => {
|
@@ -11,18 +12,20 @@ module Pronto
|
|
11
12
|
'slug' => nil,
|
12
13
|
'access_token' => nil,
|
13
14
|
'api_endpoint' => 'https://api.github.com/',
|
14
|
-
'web_endpoint' => 'https://github.com/'
|
15
|
+
'web_endpoint' => 'https://github.com/',
|
16
|
+
'review_type' => 'request_changes'
|
15
17
|
},
|
16
18
|
'gitlab' => {
|
17
19
|
'slug' => nil,
|
18
20
|
'api_private_token' => nil,
|
19
|
-
'api_endpoint' => 'https://gitlab.com/api/
|
21
|
+
'api_endpoint' => 'https://gitlab.com/api/v4'
|
20
22
|
},
|
21
23
|
'bitbucket' => {
|
22
24
|
'slug' => nil,
|
23
25
|
'username' => nil,
|
24
26
|
'password' => nil,
|
25
27
|
'api_endpoint' => nil,
|
28
|
+
'auto_approve' => false,
|
26
29
|
'web_endpoint' => 'https://bitbucket.org/'
|
27
30
|
},
|
28
31
|
'text' => {
|
@@ -31,6 +34,7 @@ module Pronto
|
|
31
34
|
'runners' => [],
|
32
35
|
'formatters' => [],
|
33
36
|
'max_warnings' => nil,
|
37
|
+
'warnings_per_review' => DEFAULT_WARNINGS_PER_REVIEW,
|
34
38
|
'verbose' => false,
|
35
39
|
'format' => DEFAULT_MESSAGE_FORMAT
|
36
40
|
}.freeze
|
@@ -51,7 +55,7 @@ module Pronto
|
|
51
55
|
if oldval.is_a?(Hash) && newval.is_a?(Hash)
|
52
56
|
oldval.merge(newval, &merger)
|
53
57
|
else
|
54
|
-
oldval
|
58
|
+
oldval.nil? ? newval : oldval
|
55
59
|
end
|
56
60
|
end
|
57
61
|
|
@@ -12,6 +12,16 @@ module Pronto
|
|
12
12
|
def line_number(message, _)
|
13
13
|
message.line.line.new_lineno if message.line
|
14
14
|
end
|
15
|
+
|
16
|
+
def approve_pull_request(comments_count, additions_count, client)
|
17
|
+
return if config.bitbucket_auto_approve == false
|
18
|
+
|
19
|
+
if comments_count > 0 && additions_count > 0
|
20
|
+
client.unapprove_pull_request
|
21
|
+
elsif comments_count == 0
|
22
|
+
client.approve_pull_request
|
23
|
+
end
|
24
|
+
end
|
15
25
|
end
|
16
26
|
end
|
17
27
|
end
|
@@ -13,9 +13,11 @@ module Pronto
|
|
13
13
|
FORMATTERS = {
|
14
14
|
'github' => GithubFormatter,
|
15
15
|
'github_status' => GithubStatusFormatter,
|
16
|
+
'github_combined_status' => GithubCombinedStatusFormatter,
|
16
17
|
'github_pr' => GithubPullRequestFormatter,
|
17
18
|
'github_pr_review' => GithubPullRequestReviewFormatter,
|
18
19
|
'gitlab' => GitlabFormatter,
|
20
|
+
'gitlab_mr' => GitlabMergeRequestReviewFormatter,
|
19
21
|
'bitbucket' => BitbucketFormatter,
|
20
22
|
'bitbucket_pr' => BitbucketPullRequestFormatter,
|
21
23
|
'bitbucket_server_pr' => BitbucketServerPullRequestFormatter,
|
@@ -7,6 +7,8 @@ module Pronto
|
|
7
7
|
comments = new_comments(messages, patches)
|
8
8
|
additions = remove_duplicate_comments(existing, comments)
|
9
9
|
submit_comments(client, additions)
|
10
|
+
|
11
|
+
approve_pull_request(comments.count, additions.count, client) if defined?(self.approve_pull_request)
|
10
12
|
|
11
13
|
"#{additions.count} Pronto messages posted to #{pretty_name}"
|
12
14
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'github_status_formatter/status_builder'
|
2
|
+
|
3
|
+
module Pronto
|
4
|
+
module Formatter
|
5
|
+
class GithubCombinedStatusFormatter
|
6
|
+
def format(messages, repo, _)
|
7
|
+
client = Github.new(repo)
|
8
|
+
head = repo.head_commit_sha
|
9
|
+
|
10
|
+
create_status(client, head, messages.uniq || [])
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def create_status(client, sha, messages)
|
16
|
+
builder = GithubStatusFormatter::StatusBuilder.new(nil, messages)
|
17
|
+
status = Status.new(sha, builder.state,
|
18
|
+
'pronto', builder.description)
|
19
|
+
|
20
|
+
client.create_commit_status(status)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -10,7 +10,7 @@ module Pronto
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def submit_comments(client, comments)
|
13
|
-
client.
|
13
|
+
client.publish_pull_request_comments(comments)
|
14
14
|
rescue Octokit::UnprocessableEntity, HTTParty::Error => e
|
15
15
|
$stderr.puts "Failed to post: #{e.message}"
|
16
16
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Pronto
|
2
|
+
module Formatter
|
3
|
+
class GitlabMergeRequestReviewFormatter < PullRequestFormatter
|
4
|
+
def client_module
|
5
|
+
Gitlab
|
6
|
+
end
|
7
|
+
|
8
|
+
def pretty_name
|
9
|
+
'Gitlab'
|
10
|
+
end
|
11
|
+
|
12
|
+
def existing_comments(_, client, repo)
|
13
|
+
sha = repo.head_commit_sha
|
14
|
+
comments = client.pull_comments(sha)
|
15
|
+
grouped_comments(comments)
|
16
|
+
end
|
17
|
+
|
18
|
+
def submit_comments(client, comments)
|
19
|
+
client.create_pull_request_review(comments)
|
20
|
+
rescue => e
|
21
|
+
$stderr.puts "Failed to post: #{e.message}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def line_number(message, _)
|
25
|
+
message.line.line.new_lineno if message.line
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -3,7 +3,7 @@ require 'pronto/formatter/text_message_decorator'
|
|
3
3
|
module Pronto
|
4
4
|
module Formatter
|
5
5
|
class TextFormatter < Base
|
6
|
-
def format(messages,
|
6
|
+
def format(messages, _repo, _patches)
|
7
7
|
messages.map do |message|
|
8
8
|
message_format = config.message_format(self.class.name)
|
9
9
|
message_data = TextMessageDecorator.new(message).to_h
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
1
3
|
module Pronto
|
2
4
|
module Formatter
|
3
5
|
class TextMessageDecorator < SimpleDelegator
|
@@ -14,6 +16,7 @@ module Pronto
|
|
14
16
|
|
15
17
|
def to_h
|
16
18
|
original = __getobj__.to_h
|
19
|
+
original[:line] = __getobj__.line.new_lineno if __getobj__.line
|
17
20
|
original[:color_level] = format_level(__getobj__)
|
18
21
|
original[:color_location] = format_location(__getobj__)
|
19
22
|
original
|
data/lib/pronto/git/patch.rb
CHANGED
@@ -10,15 +10,16 @@ module Pronto
|
|
10
10
|
def diff(commit, options = nil)
|
11
11
|
target, patches = case commit
|
12
12
|
when :unstaged, :index
|
13
|
-
[
|
13
|
+
[head_commit_sha, @repo.index.diff(options)]
|
14
14
|
when :staged
|
15
|
-
[
|
15
|
+
[head_commit_sha, head.diff(@repo.index, options)]
|
16
16
|
else
|
17
17
|
merge_base = merge_base(commit)
|
18
18
|
patches = @repo.diff(merge_base, head, options)
|
19
19
|
[merge_base, patches]
|
20
20
|
end
|
21
21
|
|
22
|
+
patches.find_similar!(renames: true)
|
22
23
|
Patches.new(self, target, patches)
|
23
24
|
end
|
24
25
|
|
@@ -37,7 +38,7 @@ module Pronto
|
|
37
38
|
|
38
39
|
def commits_until(sha)
|
39
40
|
result = []
|
40
|
-
@repo.walk(
|
41
|
+
@repo.walk(head_commit_sha, Rugged::SORT_TOPO).take_while do |commit|
|
41
42
|
result << commit.oid
|
42
43
|
!commit.oid.start_with?(sha)
|
43
44
|
end
|
@@ -45,10 +46,12 @@ module Pronto
|
|
45
46
|
end
|
46
47
|
|
47
48
|
def path
|
48
|
-
Pathname.new(@repo.
|
49
|
+
Pathname.new(@repo.workdir).cleanpath
|
49
50
|
end
|
50
51
|
|
51
52
|
def blame(path, lineno)
|
53
|
+
return if new_file?(path)
|
54
|
+
|
52
55
|
Rugged::Blame.new(@repo, path, min_line: lineno, max_line: lineno,
|
53
56
|
track_copies_same_file: true,
|
54
57
|
track_copies_any_commit_copies: true)[0]
|
@@ -66,8 +69,16 @@ module Pronto
|
|
66
69
|
head.oid
|
67
70
|
end
|
68
71
|
|
72
|
+
def head_detached?
|
73
|
+
@repo.head_detached?
|
74
|
+
end
|
75
|
+
|
69
76
|
private
|
70
77
|
|
78
|
+
def new_file?(path)
|
79
|
+
@repo.status(path).include?(:index_new)
|
80
|
+
end
|
81
|
+
|
71
82
|
def empty_patches(sha)
|
72
83
|
Patches.new(self, sha, [])
|
73
84
|
end
|