pronto 0.9.5 → 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 +50 -1
- data/LICENSE +1 -1
- data/README.md +109 -7
- 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/config.rb +17 -1
- data/lib/pronto/config_file.rb +6 -2
- 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 +1 -0
- data/lib/pronto/git/patch.rb +0 -2
- data/lib/pronto/git/repository.rb +9 -2
- data/lib/pronto/github.rb +34 -19
- 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 +10 -10
- metadata +91 -76
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
|
@@ -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
|
data/lib/pronto/git/patch.rb
CHANGED
@@ -19,6 +19,7 @@ module Pronto
|
|
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]
|
@@ -72,6 +75,10 @@ module Pronto
|
|
72
75
|
|
73
76
|
private
|
74
77
|
|
78
|
+
def new_file?(path)
|
79
|
+
@repo.status(path).include?(:index_new)
|
80
|
+
end
|
81
|
+
|
75
82
|
def empty_patches(sha)
|
76
83
|
Patches.new(self, sha, [])
|
77
84
|
end
|
data/lib/pronto/github.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
require 'pronto/github_pull'
|
2
|
+
|
1
3
|
module Pronto
|
2
4
|
class Github < Client
|
5
|
+
def initialize(repo)
|
6
|
+
super(repo)
|
7
|
+
@github_pull = Pronto::GithubPull.new(client, slug)
|
8
|
+
end
|
9
|
+
|
3
10
|
def pull_comments(sha)
|
4
11
|
@comment_cache["#{pull_id}/#{sha}"] ||= begin
|
5
12
|
client.pull_comments(slug, pull_id).map do |comment|
|
@@ -38,17 +45,12 @@ module Pronto
|
|
38
45
|
end
|
39
46
|
end
|
40
47
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
comments: comments.map do |c|
|
48
|
-
{ path: c.path, position: c.position, body: c.body }
|
49
|
-
end
|
50
|
-
}
|
51
|
-
client.create_pull_request_review(slug, pull_id, options)
|
48
|
+
def publish_pull_request_comments(comments)
|
49
|
+
comments_left = comments.clone
|
50
|
+
while comments_left.any?
|
51
|
+
comments_to_publish = comments_left.slice!(0, warnings_per_review)
|
52
|
+
create_pull_request_review(comments_to_publish)
|
53
|
+
end
|
52
54
|
end
|
53
55
|
|
54
56
|
def create_commit_status(status)
|
@@ -61,6 +63,21 @@ module Pronto
|
|
61
63
|
|
62
64
|
private
|
63
65
|
|
66
|
+
def create_pull_request_review(comments)
|
67
|
+
options = {
|
68
|
+
event: @config.github_review_type,
|
69
|
+
accept: 'application/vnd.github.v3.diff+json', # https://developer.github.com/v3/pulls/reviews/#create-a-pull-request-review
|
70
|
+
comments: comments.map do |comment|
|
71
|
+
{
|
72
|
+
path: comment.path,
|
73
|
+
position: comment.position,
|
74
|
+
body: comment.body
|
75
|
+
}
|
76
|
+
end
|
77
|
+
}
|
78
|
+
client.create_pull_request_review(slug, pull_id, options)
|
79
|
+
end
|
80
|
+
|
64
81
|
def slug
|
65
82
|
return @config.github_slug if @config.github_slug
|
66
83
|
@slug ||= begin
|
@@ -80,7 +97,7 @@ module Pronto
|
|
80
97
|
end
|
81
98
|
|
82
99
|
def pull_id
|
83
|
-
|
100
|
+
env_pull_id || pull[:number].to_i
|
84
101
|
end
|
85
102
|
|
86
103
|
def pull_sha
|
@@ -89,18 +106,16 @@ module Pronto
|
|
89
106
|
|
90
107
|
def pull
|
91
108
|
@pull ||= if env_pull_id
|
92
|
-
|
109
|
+
@github_pull.pull_by_id(env_pull_id)
|
93
110
|
elsif @repo.branch
|
94
|
-
|
111
|
+
@github_pull.pull_by_branch(@repo.branch)
|
95
112
|
elsif @repo.head_detached?
|
96
|
-
|
97
|
-
pr[:head][:sha] == @repo.head_commit_sha
|
98
|
-
end
|
113
|
+
@github_pull.pull_by_commit(@repo.head_commit_sha)
|
99
114
|
end
|
100
115
|
end
|
101
116
|
|
102
|
-
def
|
103
|
-
@
|
117
|
+
def warnings_per_review
|
118
|
+
@warnings_per_review ||= @config.warnings_per_review
|
104
119
|
end
|
105
120
|
end
|
106
121
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Pronto
|
2
|
+
# Provides strategies for finding corresponding PR on GitHub
|
3
|
+
class GithubPull
|
4
|
+
def initialize(client, slug)
|
5
|
+
@client = client
|
6
|
+
@slug = slug
|
7
|
+
end
|
8
|
+
|
9
|
+
def pull_requests
|
10
|
+
@pull_requests ||= @client.pull_requests(@slug)
|
11
|
+
end
|
12
|
+
|
13
|
+
def pull_by_id(pull_id)
|
14
|
+
result = pull_requests.find { |pr| pr[:number].to_i == pull_id }
|
15
|
+
unless result
|
16
|
+
message = "Pull request ##{pull_id} was not found in #{@slug}."
|
17
|
+
raise Pronto::Error, message
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def pull_by_branch(branch)
|
23
|
+
result = pull_requests.find { |pr| pr[:head][:ref] == branch }
|
24
|
+
unless result
|
25
|
+
raise Pronto::Error, "Pull request for branch #{branch} " \
|
26
|
+
"was not found in #{@slug}."
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
def pull_by_commit(sha)
|
32
|
+
result = pull_requests.find do |pr|
|
33
|
+
pr[:head][:sha] == sha
|
34
|
+
end
|
35
|
+
unless result
|
36
|
+
message = "Pull request with head #{sha} " \
|
37
|
+
"was not found in #{@slug}."
|
38
|
+
raise Pronto::Error, message
|
39
|
+
end
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/pronto/gitlab.rb
CHANGED
@@ -2,12 +2,49 @@ module Pronto
|
|
2
2
|
class Gitlab < Client
|
3
3
|
def commit_comments(sha)
|
4
4
|
@comment_cache[sha.to_s] ||= begin
|
5
|
-
client.commit_comments(slug, sha
|
5
|
+
client.commit_comments(slug, sha).auto_paginate.map do |comment|
|
6
6
|
Comment.new(sha, comment.note, comment.path, comment.line)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
def pull_comments(sha)
|
12
|
+
@comment_cache["#{slug}/#{pull_id}"] ||= begin
|
13
|
+
arr = []
|
14
|
+
client.merge_request_discussions(slug, pull_id).auto_paginate.each do |comment|
|
15
|
+
comment.notes.each do |note|
|
16
|
+
next unless note['position']
|
17
|
+
|
18
|
+
arr << Comment.new(
|
19
|
+
sha,
|
20
|
+
note['body'],
|
21
|
+
note['position']['new_path'],
|
22
|
+
note['position']['new_line']
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
arr
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_pull_request_review(comments)
|
31
|
+
return if comments.empty?
|
32
|
+
|
33
|
+
comments.each do |comment|
|
34
|
+
options = {
|
35
|
+
body: comment.body,
|
36
|
+
position: position_sha.dup.merge(
|
37
|
+
new_path: comment.path,
|
38
|
+
position_type: 'text',
|
39
|
+
new_line: comment.position,
|
40
|
+
old_line: nil,
|
41
|
+
)
|
42
|
+
}
|
43
|
+
|
44
|
+
client.create_merge_request_discussion(slug, pull_id, options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
11
48
|
def create_commit_comment(comment)
|
12
49
|
@config.logger.log("Creating commit comment on #{comment.sha}")
|
13
50
|
client.create_commit_comment(slug, comment.sha, comment.body,
|
@@ -17,6 +54,15 @@ module Pronto
|
|
17
54
|
|
18
55
|
private
|
19
56
|
|
57
|
+
def position_sha
|
58
|
+
# Better to get those informations from Gitlab API directly than trying to look for them here.
|
59
|
+
# (FYI you can't use `pull` method because index api does not contains those informations)
|
60
|
+
@position_sha ||= begin
|
61
|
+
data = client.merge_request(slug, pull_id)
|
62
|
+
data.diff_refs.to_h
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
20
66
|
def slug
|
21
67
|
return @config.gitlab_slug if @config.gitlab_slug
|
22
68
|
@slug ||= begin
|
@@ -27,6 +73,17 @@ module Pronto
|
|
27
73
|
end
|
28
74
|
end
|
29
75
|
|
76
|
+
def pull_id
|
77
|
+
env_pull_id || raise(Pronto::Error, "Unable to determine merge request id. Specify either `PRONTO_PULL_REQUEST_ID` or `CI_MERGE_REQUEST_IID`.")
|
78
|
+
end
|
79
|
+
|
80
|
+
def env_pull_id
|
81
|
+
pull_request = super
|
82
|
+
|
83
|
+
pull_request ||= ENV['CI_MERGE_REQUEST_IID']
|
84
|
+
pull_request.to_i if pull_request
|
85
|
+
end
|
86
|
+
|
30
87
|
def slug_regex(url)
|
31
88
|
if url =~ %r{^ssh:\/\/}
|
32
89
|
%r{.*#{host}(:[0-9]+)?(:|\/)(?<slug>.*).git}
|