pronto 0.10.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +3 -0
- data/.github/workflows/pronto.yml +20 -0
- data/.github/workflows/specs.yml +41 -0
- data/CHANGELOG.md +58 -1
- data/README.md +113 -5
- data/lib/pronto/cli.rb +6 -2
- data/lib/pronto/clients/bitbucket_client.rb +46 -18
- data/lib/pronto/config.rb +56 -3
- data/lib/pronto/config_file.rb +8 -2
- data/lib/pronto/formatter/checkstyle_formatter.rb +1 -1
- data/lib/pronto/formatter/formatter.rb +2 -0
- data/lib/pronto/formatter/git_formatter.rb +1 -1
- 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/git/patch.rb +0 -2
- data/lib/pronto/git/repository.rb +14 -2
- data/lib/pronto/github.rb +25 -11
- data/lib/pronto/gitlab.rb +61 -2
- data/lib/pronto/runner.rb +8 -1
- data/lib/pronto/runners.rb +23 -6
- data/lib/pronto/version.rb +1 -1
- data/lib/pronto.rb +8 -2
- data/pronto.gemspec +9 -7
- metadata +113 -28
@@ -10,7 +10,7 @@ module Pronto
|
|
10
10
|
|
11
11
|
approve_pull_request(comments.count, additions.count, client) if defined?(self.approve_pull_request)
|
12
12
|
|
13
|
-
"#{additions.count} Pronto messages posted to #{pretty_name}"
|
13
|
+
"#{additions.count} Pronto messages posted to #{pretty_name} (#{existing.count} existing)"
|
14
14
|
end
|
15
15
|
|
16
16
|
def client_module
|
@@ -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
@@ -13,6 +13,18 @@ module Pronto
|
|
13
13
|
[head_commit_sha, @repo.index.diff(options)]
|
14
14
|
when :staged
|
15
15
|
[head_commit_sha, head.diff(@repo.index, options)]
|
16
|
+
when :workdir
|
17
|
+
[
|
18
|
+
head_commit_sha,
|
19
|
+
@repo.diff_workdir(
|
20
|
+
head,
|
21
|
+
{
|
22
|
+
include_untracked: true,
|
23
|
+
include_untracked_content: true,
|
24
|
+
recurse_untracked_dirs: true
|
25
|
+
}.merge(options || {})
|
26
|
+
)
|
27
|
+
]
|
16
28
|
else
|
17
29
|
merge_base = merge_base(commit)
|
18
30
|
patches = @repo.diff(merge_base, head, options)
|
@@ -38,7 +50,7 @@ module Pronto
|
|
38
50
|
|
39
51
|
def commits_until(sha)
|
40
52
|
result = []
|
41
|
-
@repo.walk(
|
53
|
+
@repo.walk(head_commit_sha, Rugged::SORT_TOPO).take_while do |commit|
|
42
54
|
result << commit.oid
|
43
55
|
!commit.oid.start_with?(sha)
|
44
56
|
end
|
@@ -46,7 +58,7 @@ module Pronto
|
|
46
58
|
end
|
47
59
|
|
48
60
|
def path
|
49
|
-
Pathname.new(@repo.
|
61
|
+
Pathname.new(@repo.workdir).cleanpath
|
50
62
|
end
|
51
63
|
|
52
64
|
def blame(path, lineno)
|
data/lib/pronto/github.rb
CHANGED
@@ -45,17 +45,12 @@ module Pronto
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
comments: comments.map do |c|
|
55
|
-
{ path: c.path, position: c.position, body: c.body }
|
56
|
-
end
|
57
|
-
}
|
58
|
-
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
|
59
54
|
end
|
60
55
|
|
61
56
|
def create_commit_status(status)
|
@@ -68,6 +63,21 @@ module Pronto
|
|
68
63
|
|
69
64
|
private
|
70
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
|
+
|
71
81
|
def slug
|
72
82
|
return @config.github_slug if @config.github_slug
|
73
83
|
@slug ||= begin
|
@@ -103,5 +113,9 @@ module Pronto
|
|
103
113
|
@github_pull.pull_by_commit(@repo.head_commit_sha)
|
104
114
|
end
|
105
115
|
end
|
116
|
+
|
117
|
+
def warnings_per_review
|
118
|
+
@warnings_per_review ||= @config.warnings_per_review
|
119
|
+
end
|
106
120
|
end
|
107
121
|
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,11 +73,24 @@ 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}
|
33
|
-
|
90
|
+
elsif url =~ /#{host}/
|
34
91
|
%r{.*#{host}(:|\/)(?<slug>.*).git}
|
92
|
+
else
|
93
|
+
%r{\/\/.*?(\/)(?<slug>.*).git}
|
35
94
|
end
|
36
95
|
end
|
37
96
|
|
data/lib/pronto/runner.rb
CHANGED
@@ -28,7 +28,10 @@ module Pronto
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def ruby_file?(path)
|
31
|
-
rb_file?(path) ||
|
31
|
+
rb_file?(path) ||
|
32
|
+
rake_file?(path) ||
|
33
|
+
gem_file?(path) ||
|
34
|
+
ruby_executable?(path)
|
32
35
|
end
|
33
36
|
|
34
37
|
def repo_path
|
@@ -45,6 +48,10 @@ module Pronto
|
|
45
48
|
File.extname(path) == '.rake'
|
46
49
|
end
|
47
50
|
|
51
|
+
def gem_file?(path)
|
52
|
+
File.basename(path) == 'Gemfile' || File.extname(path) == '.gemspec'
|
53
|
+
end
|
54
|
+
|
48
55
|
def ruby_executable?(path)
|
49
56
|
return false if File.directory?(path)
|
50
57
|
line = File.open(path, &:readline)
|
data/lib/pronto/runners.rb
CHANGED
@@ -6,25 +6,42 @@ module Pronto
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def run(patches)
|
9
|
-
patches = reject_excluded(
|
9
|
+
patches = reject_excluded(config.excluded_files('all'), patches)
|
10
10
|
return [] if patches.none?
|
11
11
|
|
12
12
|
result = []
|
13
|
-
|
13
|
+
active_runners.each do |runner|
|
14
14
|
next if exceeds_max?(result)
|
15
|
-
|
15
|
+
config.logger.log("Running #{runner}")
|
16
16
|
runner_patches = reject_excluded(
|
17
|
-
|
17
|
+
config.excluded_files(runner.title), patches
|
18
18
|
)
|
19
19
|
next if runner_patches.none?
|
20
20
|
result += runner.new(runner_patches, patches.commit).run.flatten.compact
|
21
21
|
end
|
22
|
-
result = result.take(
|
22
|
+
result = result.take(config.max_warnings) if config.max_warnings
|
23
23
|
result
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
+
attr_reader :config, :runners
|
29
|
+
|
30
|
+
def active_runners
|
31
|
+
runners.select { |runner| active_runner?(runner) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def active_runner?(runner)
|
35
|
+
return true if config.runners.empty? && config.skip_runners.empty?
|
36
|
+
|
37
|
+
if config.runners.empty?
|
38
|
+
!config.skip_runners.include?(runner.title)
|
39
|
+
else
|
40
|
+
active_runner_names = config.runners - config.skip_runners
|
41
|
+
active_runner_names.include?(runner.title)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
28
45
|
def reject_excluded(excluded_files, patches)
|
29
46
|
return patches unless excluded_files.any?
|
30
47
|
|
@@ -34,7 +51,7 @@ module Pronto
|
|
34
51
|
end
|
35
52
|
|
36
53
|
def exceeds_max?(warnings)
|
37
|
-
|
54
|
+
config.max_warnings && warnings.count >= config.max_warnings
|
38
55
|
end
|
39
56
|
end
|
40
57
|
end
|
data/lib/pronto/version.rb
CHANGED
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'
|
@@ -53,9 +55,9 @@ require 'pronto/formatter/null_formatter'
|
|
53
55
|
require 'pronto/formatter/formatter'
|
54
56
|
|
55
57
|
module Pronto
|
56
|
-
def self.run(commit =
|
58
|
+
def self.run(commit = nil, repo_path = '.',
|
57
59
|
formatters = [Formatter::TextFormatter.new], file = nil)
|
58
|
-
commit ||=
|
60
|
+
commit ||= default_commit
|
59
61
|
|
60
62
|
repo = Git::Repository.new(repo_path)
|
61
63
|
options = { paths: [file] } if file
|
@@ -70,4 +72,8 @@ module Pronto
|
|
70
72
|
|
71
73
|
result
|
72
74
|
end
|
75
|
+
|
76
|
+
def self.default_commit
|
77
|
+
Config.new.default_commit
|
78
|
+
end
|
73
79
|
end
|
data/pronto.gemspec
CHANGED
@@ -40,18 +40,20 @@ Gem::Specification.new do |s|
|
|
40
40
|
s.require_paths = ['lib']
|
41
41
|
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
42
42
|
|
43
|
-
s.add_runtime_dependency('gitlab', '
|
44
|
-
s.add_runtime_dependency('httparty', '>= 0.13.7')
|
45
|
-
s.add_runtime_dependency('octokit', '
|
43
|
+
s.add_runtime_dependency('gitlab', '>= 4.4.0', '< 5.0')
|
44
|
+
s.add_runtime_dependency('httparty', '>= 0.13.7', '< 1.0')
|
45
|
+
s.add_runtime_dependency('octokit', '>= 4.7.0', '< 7.0')
|
46
46
|
s.add_runtime_dependency('rainbow', '>= 2.2', '< 4.0')
|
47
|
-
s.add_runtime_dependency('
|
48
|
-
s.add_runtime_dependency('
|
47
|
+
s.add_runtime_dependency('rexml', '>= 3.2.5', '< 4.0')
|
48
|
+
s.add_runtime_dependency('rugged', '>= 0.23.0', '< 2.0')
|
49
|
+
s.add_runtime_dependency('thor', '>= 0.20.3', '< 2.0')
|
49
50
|
s.add_development_dependency('bundler', '>= 1.15')
|
50
|
-
s.add_development_dependency('pronto-rubocop', '~> 0.
|
51
|
+
s.add_development_dependency('pronto-rubocop', '~> 0.10.0')
|
51
52
|
s.add_development_dependency('rake', '~> 12.0')
|
52
53
|
s.add_development_dependency('rspec', '~> 3.4')
|
53
54
|
s.add_development_dependency('rspec-its', '~> 1.2')
|
54
55
|
s.add_development_dependency('rspec-expectations', '~> 3.4')
|
55
56
|
s.add_development_dependency('rubocop', '~> 0.58')
|
56
|
-
s.add_development_dependency('simplecov', '~> 0.
|
57
|
+
s.add_development_dependency('simplecov', '~> 0.17', '!= 0.18.0', '!= 0.18.1', '!= 0.18.2', '!= 0.18.3', '!= 0.18.4',
|
58
|
+
'!= 0.18.5', '!= 0.19.0', '!= 0.19.1') # see https://docs.codeclimate.com/docs/configuring-test-coverage
|
57
59
|
end
|